kioslaves

sieve.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002                           sieve.cpp  -  description
00003                              -------------------
00004     begin                : Thu Dec 20 18:47:08 EST 2001
00005     copyright            : (C) 2001 by Hamish Rodda
00006     email                : meddie@yoyo.cc.monash.edu.au
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License version 2 as     *
00013  *   published by the Free Software Foundation.                            *
00014  *                                                                         *
00015  ***************************************************************************/
00016 
00027 #ifdef HAVE_CONFIG_H
00028 # include <config.h>
00029 #endif
00030 
00031 extern "C" {
00032 #include <sasl/sasl.h>
00033 }
00034 #include "sieve.h"
00035 
00036 #include <kdebug.h>
00037 #include <kinstance.h>
00038 #include <klocale.h>
00039 #include <kurl.h>
00040 #include <kmdcodec.h>
00041 #include <kglobal.h>
00042 
00043 #include <qcstring.h>
00044 #include <qregexp.h>
00045 
00046 #include <cstdlib>
00047 using std::exit;
00048 #include <sys/stat.h>
00049 
00050 #include <kdepimmacros.h>
00051 
00052 static const int debugArea = 7122;
00053 
00054 static inline
00055 #ifdef NDEBUG
00056   kndbgstream ksDebug() { return kdDebug( debugArea ); }
00057   kndbgstream ksDebug( bool cond ) { return kdDebug( cond, debugArea ); }
00058 #else
00059   kdbgstream ksDebug() { return kdDebug( debugArea ); }
00060   kdbgstream ksDebug( bool cond ) { return kdDebug( cond, debugArea ); }
00061 #endif
00062 
00063 #define SIEVE_DEFAULT_PORT 2000
00064 
00065 static sasl_callback_t callbacks[] = {
00066     { SASL_CB_ECHOPROMPT, NULL, NULL },
00067     { SASL_CB_NOECHOPROMPT, NULL, NULL },
00068     { SASL_CB_GETREALM, NULL, NULL },
00069     { SASL_CB_USER, NULL, NULL },
00070     { SASL_CB_AUTHNAME, NULL, NULL },
00071     { SASL_CB_PASS, NULL, NULL },
00072     { SASL_CB_CANON_USER, NULL, NULL },
00073     { SASL_CB_LIST_END, NULL, NULL }
00074 };
00075 
00076 static const unsigned int SIEVE_DEFAULT_RECIEVE_BUFFER = 512;
00077 
00078 using namespace KIO;
00079 extern "C"
00080 {
00081     KDE_EXPORT int kdemain(int argc, char **argv)
00082     {
00083         KInstance instance("kio_sieve" );
00084 
00085         ksDebug() << "*** Starting kio_sieve " << endl;
00086 
00087         if (argc != 4) {
00088             ksDebug() << "Usage: kio_sieve protocol domain-socket1 domain-socket2" << endl;
00089             exit(-1);
00090         }
00091 
00092     if ( sasl_client_init( NULL ) != SASL_OK ) {
00093       fprintf(stderr, "SASL library initialization failed!\n");
00094       ::exit (-1);
00095     }
00096 
00097         kio_sieveProtocol slave(argv[2], argv[3]);
00098         slave.dispatchLoop();
00099 
00100     sasl_done();
00101 
00102         ksDebug() << "*** kio_sieve Done" << endl;
00103         return 0;
00104     }
00105 }
00106 
00107 /* ---------------------------------------------------------------------------------- */
00108 kio_sieveResponse::kio_sieveResponse()
00109 {
00110     clear();
00111 }
00112 
00113 /* ---------------------------------------------------------------------------------- */
00114 const uint& kio_sieveResponse::getType() const
00115 {
00116     return rType;
00117 }
00118 
00119 /* ---------------------------------------------------------------------------------- */
00120 const uint kio_sieveResponse::getQuantity() const
00121 {
00122     return quantity;
00123 }
00124 
00125 /* ---------------------------------------------------------------------------------- */
00126 const QCString& kio_sieveResponse::getAction() const
00127 {
00128     return key;
00129 }
00130 
00131 /* ---------------------------------------------------------------------------------- */
00132 const QCString& kio_sieveResponse::getKey() const
00133 {
00134     return key;
00135 }
00136 
00137 /* ---------------------------------------------------------------------------------- */
00138 const QCString& kio_sieveResponse::getVal() const
00139 {
00140     return val;
00141 }
00142 
00143 /* ---------------------------------------------------------------------------------- */
00144 const QCString& kio_sieveResponse::getExtra() const
00145 {
00146     return extra;
00147 }
00148 
00149 /* ---------------------------------------------------------------------------------- */
00150 void kio_sieveResponse::setQuantity(const uint& newQty)
00151 {
00152     rType = QUANTITY;
00153     quantity = newQty;
00154 }
00155 
00156 /* ---------------------------------------------------------------------------------- */
00157 void kio_sieveResponse::setAction(const QCString& newAction)
00158 {
00159     rType = ACTION;
00160     key = newAction.copy();
00161 }
00162 
00163 /* ---------------------------------------------------------------------------------- */
00164 void kio_sieveResponse::setKey(const QCString& newKey)
00165 {
00166     rType = KEY_VAL_PAIR;
00167     key = newKey.copy();
00168 }
00169 
00170 /* ---------------------------------------------------------------------------------- */
00171 void kio_sieveResponse::setVal(const QCString& newVal)
00172 {
00173     val = newVal.copy();
00174 }
00175 
00176 /* ---------------------------------------------------------------------------------- */
00177 void kio_sieveResponse::setExtra(const QCString& newExtra)
00178 {
00179     extra = newExtra.copy();
00180 }
00181 
00182 /* ---------------------------------------------------------------------------------- */
00183 void kio_sieveResponse::clear()
00184 {
00185     rType = NONE;
00186     extra = key = val = QCString("");
00187     quantity = 0;
00188 }
00189 
00190 /* ---------------------------------------------------------------------------------- */
00191 kio_sieveProtocol::kio_sieveProtocol(const QCString &pool_socket, const QCString &app_socket)
00192     : TCPSlaveBase( SIEVE_DEFAULT_PORT, "sieve", pool_socket, app_socket, false)
00193     , m_connMode(NORMAL)
00194     , m_supportsTLS(false)
00195     , m_shouldBeConnected(false)
00196 {
00197 }
00198 
00199 /* ---------------------------------------------------------------------------------- */
00200 kio_sieveProtocol::~kio_sieveProtocol()
00201 {
00202     if ( isConnectionValid() )
00203         disconnect();
00204 }
00205 
00206 /* ---------------------------------------------------------------------------------- */
00207 void kio_sieveProtocol::setHost (const QString &host, int port, const QString &user, const QString &pass)
00208 {
00209     if ( isConnectionValid() &&
00210             ( m_sServer != host ||
00211                 m_iPort != port ||
00212                 m_sUser != user ||
00213                 m_sPass != pass ) ) {
00214         disconnect();
00215     }
00216     m_sServer = host;
00217     m_iPort = port ? port : m_iDefaultPort;
00218     m_sUser = user;
00219     m_sPass = pass;
00220     m_supportsTLS = false;
00221 }
00222 
00223 /* ---------------------------------------------------------------------------------- */
00224 void kio_sieveProtocol::openConnection()
00225 {
00226     m_connMode = CONNECTION_ORIENTED;
00227     connect();
00228 }
00229 
00230 bool kio_sieveProtocol::parseCapabilities(bool requestCapabilities/* = false*/)
00231 {
00232     ksDebug() << k_funcinfo << endl;
00233 
00234     // Setup...
00235     bool ret = false;
00236 
00237     if (requestCapabilities) {
00238         sendData("CAPABILITY");
00239     }
00240 
00241     while (receiveData()) {
00242         ksDebug() << "Looping receive" << endl;
00243 
00244         if (r.getType() == kio_sieveResponse::ACTION) {
00245             if ( r.getAction().contains("ok", false) != -1 ) {
00246                 ksDebug() << "Sieve server ready & awaiting authentication." << endl;
00247                 break;
00248             } else
00249                 ksDebug() << "Unknown action " << r.getAction() << "." << endl;
00250 
00251         } else if (r.getKey() == "IMPLEMENTATION") {
00252             if (r.getVal().contains("sieve", false) != -1) {
00253                 ksDebug() << "Connected to Sieve server: " << r.getVal() << endl;
00254                 ret = true;
00255                 setMetaData("implementation", r.getVal());
00256                 m_implementation = r.getVal();
00257             }
00258 
00259         } else if (r.getKey() == "SASL") {
00260             // Save list of available SASL methods
00261             m_sasl_caps = QStringList::split(' ', r.getVal());
00262             ksDebug() << "Server SASL authentication methods: " << m_sasl_caps.join(", ") << endl;
00263             setMetaData("saslMethods", r.getVal());
00264 
00265         } else if (r.getKey() == "SIEVE") {
00266             // Save script capabilities; report back as meta data:
00267             ksDebug() << "Server script capabilities: " << QStringList::split(' ', r.getVal()).join(", ") << endl;
00268             setMetaData("sieveExtensions", r.getVal());
00269 
00270         } else if (r.getKey() == "STARTTLS") {
00271             // The server supports TLS
00272             ksDebug() << "Server supports TLS" << endl;
00273             m_supportsTLS = true;
00274             setMetaData("tlsSupported", "true");
00275 
00276         } else {
00277             ksDebug() << "Unrecognised key " << r.getKey() << endl;
00278         }
00279     }
00280 
00281     if (!m_supportsTLS) {
00282         setMetaData("tlsSupported", "false");
00283     }
00284 
00285     return ret;
00286 }
00287 
00288 
00289 /* ---------------------------------------------------------------------------------- */
00294 void kio_sieveProtocol::changeCheck( const KURL &url )
00295 {
00296     QString auth;
00297 
00298     if (!metaData("sasl").isEmpty())
00299         auth = metaData("sasl").upper();
00300     else {
00301         QString query = url.query();
00302         if ( query.startsWith("?") ) query.remove( 0, 1 );
00303         QStringList q = QStringList::split( ",", query );
00304         QStringList::iterator it;
00305 
00306         for ( it = q.begin(); it != q.end(); ++it ) {
00307             if ( ( (*it).section('=',0,0) ).lower() == "x-mech" ) {
00308                 auth = ( (*it).section('=',1) ).upper();
00309                 break;
00310             }
00311         }
00312     }
00313     ksDebug() << "auth: " << auth << " m_sAuth: " << m_sAuth << endl;
00314     if ( m_sAuth != auth ) {
00315         m_sAuth = auth;
00316         if ( isConnectionValid() )
00317             disconnect();
00318     }
00319 }
00320 
00321 /* ---------------------------------------------------------------------------------- */
00326 bool kio_sieveProtocol::connect(bool useTLSIfAvailable)
00327 {
00328     ksDebug() << k_funcinfo << endl;
00329 
00330     if (isConnectionValid()) return true;
00331 
00332     infoMessage(i18n("Connecting to %1...").arg( m_sServer));
00333 
00334     if (m_connMode == CONNECTION_ORIENTED && m_shouldBeConnected) {
00335         error(ERR_CONNECTION_BROKEN, i18n("The connection to the server was lost."));
00336         return false;
00337     }
00338 
00339     setBlockConnection(true);
00340 
00341     if (!connectToHost(m_sServer, m_iPort, true)) {
00342         return false;
00343     }
00344 
00345     if (!parseCapabilities()) {
00346         closeDescriptor();
00347         error(ERR_UNSUPPORTED_PROTOCOL, i18n("Server identification failed."));
00348         return false;
00349     }
00350 
00351     // Attempt to start TLS
00352     // FIXME find a test server and test that this works
00353     if (useTLSIfAvailable && m_supportsTLS && canUseTLS()) {
00354         sendData("STARTTLS");
00355         if (operationSuccessful()) {
00356             ksDebug() << "TLS has been accepted. Starting TLS..." << endl
00357                   << "WARNING this is untested and may fail." << endl;
00358             int retval = startTLS();
00359             if (retval == 1) {
00360                 ksDebug() << "TLS enabled successfully." << endl;
00361                 // reparse capabilities:
00362                 parseCapabilities( requestCapabilitiesAfterStartTLS() );
00363             } else {
00364                 ksDebug() << "TLS initiation failed, code " << retval << endl;
00365                 disconnect(true);
00366                 return connect(false);
00367                 // error(ERR_INTERNAL, i18n("TLS initiation failed."));
00368             }
00369         } else
00370             ksDebug() << "Server incapable of TLS. Transmitted documents will be unencrypted." << endl;
00371     } else
00372         ksDebug() << "We are incapable of TLS. Transmitted documents will be unencrypted." << endl;
00373 
00374     infoMessage(i18n("Authenticating user..."));
00375     if (!authenticate()) {
00376         disconnect();
00377         error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed."));
00378         return false;
00379     }
00380 
00381     m_shouldBeConnected = true;
00382     return true;
00383 }
00384 
00385 /* ---------------------------------------------------------------------------------- */
00386 void kio_sieveProtocol::closeConnection()
00387 {
00388     m_connMode = CONNECTION_ORIENTED;
00389     disconnect();
00390 }
00391 
00392 /* ---------------------------------------------------------------------------------- */
00393 void kio_sieveProtocol::disconnect(bool forcibly)
00394 {
00395     if (!forcibly) {
00396         sendData("LOGOUT");
00397 
00398         // This crashes under certain conditions as described in
00399         // http://intevation.de/roundup/kolab/issue2442
00400         // Fixing KIO::TCPSlaveBase::atEnd() for !fd would also work but 3.x is on life support.
00401         //if (!operationSuccessful())
00402         //  ksDebug() << "Server did not logout cleanly." << endl;
00403     }
00404 
00405     closeDescriptor();
00406     m_shouldBeConnected = false;
00407 }
00408 
00409 /* ---------------------------------------------------------------------------------- */
00410 /*void kio_sieveProtocol::slave_status()
00411 {
00412     slaveStatus(isConnectionValid() ? m_sServer : "", isConnectionValid());
00413 
00414     finished();
00415 }*/
00416 
00417 /* ---------------------------------------------------------------------------------- */
00418 void kio_sieveProtocol::special(const QByteArray &data)
00419 {
00420     int tmp;
00421     QDataStream stream(data, IO_ReadOnly);
00422     KURL url;
00423 
00424     stream >> tmp;
00425 
00426     switch (tmp) {
00427         case 1:
00428             stream >> url;
00429             if (!activate(url))
00430                 return;
00431             break;
00432         case 2:
00433             if (!deactivate())
00434                 return;
00435             break;
00436         case 3:
00437             parseCapabilities(true);
00438             break;
00439     }
00440 
00441     infoMessage(i18n("Done."));
00442 
00443     finished();
00444 }
00445 
00446 /* ---------------------------------------------------------------------------------- */
00447 bool kio_sieveProtocol::activate(const KURL& url)
00448 {
00449     changeCheck( url );
00450     if (!connect())
00451         return false;
00452 
00453     infoMessage(i18n("Activating script..."));
00454 
00455     QString filename = url.fileName(false);
00456 
00457     if (filename.isEmpty()) {
00458         error(ERR_DOES_NOT_EXIST, url.prettyURL());
00459         return false;
00460     }
00461 
00462     if (!sendData("SETACTIVE \"" + filename.utf8() + "\""))
00463         return false;
00464 
00465     if (operationSuccessful()) {
00466         ksDebug() << "Script activation complete." << endl;
00467         return true;
00468     } else {
00469         error(ERR_INTERNAL_SERVER, i18n("There was an error activating the script."));
00470         return false;
00471     }
00472 }
00473 
00474 /* ---------------------------------------------------------------------------------- */
00475 bool kio_sieveProtocol::deactivate()
00476 {
00477     if (!connect())
00478         return false;
00479 
00480     if (!sendData("SETACTIVE \"\""))
00481         return false;
00482 
00483     if (operationSuccessful()) {
00484         ksDebug() << "Script deactivation complete." << endl;
00485         return true;
00486     } else {
00487         error(ERR_INTERNAL_SERVER, i18n("There was an error deactivating the script."));
00488         return false;
00489     }
00490 }
00491 
00492 static void append_lf2crlf( QByteArray & out, const QByteArray & in ) {
00493   if ( in.isEmpty() )
00494     return;
00495   const unsigned int oldOutSize = out.size();
00496   out.resize( oldOutSize + 2 * in.size() );
00497   const char * s = in.begin();
00498   const char * const end = in.end();
00499   char * d = out.begin() + oldOutSize;
00500   char last = '\0';
00501   while ( s < end ) {
00502     if ( *s == '\n' && last != '\r' )
00503       *d++ = '\r';
00504     *d++ = last = *s++;
00505   }
00506   out.resize( d - out.begin() );
00507 }
00508 
00509 void kio_sieveProtocol::put(const KURL& url, int /*permissions*/, bool /*overwrite*/, bool /*resume*/)
00510 {
00511     changeCheck( url );
00512     if (!connect())
00513         return;
00514 
00515     infoMessage(i18n("Sending data..."));
00516 
00517     QString filename = url.fileName(false);
00518 
00519     if (filename.isEmpty()) {
00520         error(ERR_MALFORMED_URL, url.prettyURL());
00521         return;
00522     }
00523 
00524     QByteArray data;
00525     for (;;) {
00526         dataReq();
00527         QByteArray buffer;
00528         const int newSize = readData(buffer);
00529         append_lf2crlf( data, buffer );
00530         if ( newSize < 0 ) {
00531           // read error: network in unknown state so disconnect
00532           error(ERR_COULD_NOT_READ, i18n("KIO data supply error."));
00533           return;
00534         }
00535         if ( newSize == 0 )
00536           break;
00537     }
00538 
00539     // script size
00540     int bufLen = (int)data.size();
00541     totalSize(bufLen);
00542 
00543     // timsieved 1.1.0:
00544     // C: HAVESPACE "rejected" 74
00545     // S: NO "Number expected"
00546     // C: HAVESPACE 74
00547     // S: NO "Missing script name"
00548     // S: HAVESPACE "rejected" "74"
00549     // C: NO "Number expected"
00550     // => broken, we can't use it :-(
00551     // (will be fixed in Cyrus 2.1.10)
00552 #ifndef HAVE_BROKEN_TIMSIEVED
00553     // first, check quota (it's a SHOULD in draft std)
00554     if (!sendData("HAVESPACE \"" + filename.utf8() + "\" "
00555               + QCString().setNum( bufLen )))
00556         return;
00557 
00558     if (!operationSuccessful()) {
00559         error(ERR_DISK_FULL, i18n("Quota exceeded"));
00560         return;
00561     }
00562 #endif
00563 
00564     if (!sendData("PUTSCRIPT \"" + filename.utf8() + "\" {"
00565               + QCString().setNum( bufLen ) + "+}"))
00566         return;
00567 
00568     // atEnd() lies so the code below doesn't work.
00569     /*if (!atEnd()) {
00570         // We are not expecting any data here, so if the server has responded
00571         // with anything but OK we treat it as an error.
00572         char * buf = new char[2];
00573         while (!atEnd()) {
00574             ksDebug() << "Reading..." << endl;
00575             read(buf, 1);
00576             ksDebug() << "Trailing [" << buf[0] << "]" << endl;
00577         }
00578         ksDebug() << "End of data." << endl;
00579         delete[] buf;
00580 
00581         if (!operationSuccessful()) {
00582             error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred "
00583                         "while trying to negotiate script uploading.\n"
00584                         "The server responded:\n%1")
00585                             .arg(r.getAction().right(r.getAction().length() - 3)));
00586             return;
00587         }
00588     }*/
00589 
00590     // upload data to the server
00591     if (write(data, bufLen) != bufLen) {
00592         error(ERR_COULD_NOT_WRITE, i18n("Network error."));
00593         disconnect(true);
00594         return;
00595     }
00596 
00597     // finishing CR/LF
00598     if (!sendData(""))
00599         return;
00600 
00601     processedSize(bufLen);
00602 
00603     infoMessage(i18n("Verifying upload completion..."));
00604 
00605     if (operationSuccessful())
00606         ksDebug() << "Script upload complete." << endl;
00607 
00608     else {
00609         /* The managesieve server parses received scripts and rejects
00610          * scripts which are not syntactically correct. Here we expect
00611          * to receive a message detailing the error (only the first
00612          * error is reported. */
00613         if (r.getAction().length() > 3) {
00614             // make a copy of the extra info
00615             QCString extra = r.getAction().right(r.getAction().length() - 3);
00616 
00617             // send the extra message off for re-processing
00618             receiveData(false, &extra);
00619 
00620             if (r.getType() == kio_sieveResponse::QUANTITY) {
00621                 // length of the error message
00622                 uint len = r.getQuantity();
00623 
00624                 QCString errmsg(len + 1);
00625 
00626                 read(errmsg.data(), len);
00627 
00628                 error(ERR_INTERNAL_SERVER,
00629                         i18n("The script did not upload successfully.\n"
00630                             "This is probably due to errors in the script.\n"
00631                             "The server responded:\n%1").arg(errmsg));
00632 
00633                 // clear the rest of the incoming data
00634                 receiveData();
00635             } else if (r.getType() == kio_sieveResponse::KEY_VAL_PAIR) {
00636                 error(ERR_INTERNAL_SERVER,
00637                         i18n("The script did not upload successfully.\n"
00638                             "This is probably due to errors in the script.\n"
00639                             "The server responded:\n%1").arg(r.getKey()));
00640             } else 
00641                 error(ERR_INTERNAL_SERVER,
00642                     i18n("The script did not upload successfully.\n"
00643                         "The script may contain errors."));
00644         } else
00645             error(ERR_INTERNAL_SERVER,
00646                 i18n("The script did not upload successfully.\n"
00647                     "The script may contain errors."));
00648     }
00649 
00650     //if ( permissions != -1 )
00651     //  chmod( url, permissions );
00652 
00653     infoMessage(i18n("Done."));
00654 
00655     finished();
00656 }
00657 
00658 static void inplace_crlf2lf( QByteArray & in ) {
00659   if ( in.isEmpty() )
00660     return;
00661   QByteArray & out = in; // inplace
00662   const char * s = in.begin();
00663   const char * const end = in.end();
00664   char * d = out.begin();
00665   char last = '\0';
00666   while ( s < end ) {
00667     if ( *s == '\n' && last == '\r' )
00668       --d;
00669     *d++ = last = *s++;
00670   }
00671   out.resize( d - out.begin() );
00672 }
00673 
00674 /* ---------------------------------------------------------------------------------- */
00675 void kio_sieveProtocol::get(const KURL& url)
00676 {
00677     changeCheck( url );
00678     if (!connect())
00679         return;
00680 
00681     infoMessage(i18n("Retrieving data..."));
00682 
00683     QString filename = url.fileName(false);
00684 
00685     if (filename.isEmpty()) {
00686         error(ERR_MALFORMED_URL, url.prettyURL());
00687         return;
00688     }
00689 
00690     //SlaveBase::mimetype( QString("text/plain") ); // "application/sieve");
00691 
00692     if (!sendData("GETSCRIPT \"" + filename.utf8() + "\""))
00693         return;
00694 
00695     if (receiveData() && r.getType() == kio_sieveResponse::QUANTITY) {
00696         // determine script size
00697         ssize_t total_len = r.getQuantity();
00698         totalSize( total_len );
00699 
00700         int recv_len = 0;
00701         do {
00702           // wait for data...
00703           if ( !waitForResponse( 600 ) ) {
00704             error( KIO::ERR_SERVER_TIMEOUT, m_sServer );
00705             disconnect( true );
00706             return;
00707           }
00708 
00709           // ...read data...
00710           // Only read as much as we need, otherwise we slurp in the OK that
00711           // operationSuccessful() is expecting below.
00712           QByteArray dat( kMin( total_len - recv_len, ssize_t(64 * 1024 )) );
00713           ssize_t this_recv_len = read( dat.data(), dat.size() );
00714 
00715           if ( this_recv_len < 1 && !isConnectionValid() ) {
00716             error( KIO::ERR_CONNECTION_BROKEN, m_sServer );
00717             disconnect( true );
00718             return;
00719           }
00720 
00721           dat.resize( this_recv_len );
00722           inplace_crlf2lf( dat );
00723           // send data to slaveinterface
00724           data( dat );
00725 
00726           recv_len += this_recv_len;
00727           processedSize( recv_len );
00728         } while ( recv_len < total_len );
00729 
00730         infoMessage(i18n("Finishing up...") );
00731         data(QByteArray());
00732 
00733         if (operationSuccessful())
00734             ksDebug() << "Script retrieval complete." << endl;
00735         else
00736             ksDebug() << "Script retrieval failed." << endl;
00737     } else {
00738         error(ERR_UNSUPPORTED_PROTOCOL, i18n("A protocol error occurred "
00739                             "while trying to negotiate script downloading."));
00740         return;
00741     }
00742 
00743     infoMessage(i18n("Done."));
00744     finished();
00745 }
00746 
00747 void kio_sieveProtocol::del(const KURL &url, bool isfile)
00748 {
00749     if (!isfile) {
00750         error(ERR_INTERNAL, i18n("Folders are not supported."));
00751         return;
00752     }
00753 
00754     changeCheck( url );
00755     if (!connect())
00756         return;
00757 
00758     infoMessage(i18n("Deleting file..."));
00759 
00760     QString filename = url.fileName(false);
00761 
00762     if (filename.isEmpty()) {
00763         error(ERR_MALFORMED_URL, url.prettyURL());
00764         return;
00765     }
00766 
00767     if (!sendData("DELETESCRIPT \"" + filename.utf8() + "\""))
00768         return;
00769 
00770     if (operationSuccessful())
00771         ksDebug() << "Script deletion successful." << endl;
00772     else {
00773         error(ERR_INTERNAL_SERVER, i18n("The server would not delete the file."));
00774         return;
00775     }
00776 
00777     infoMessage(i18n("Done."));
00778 
00779     finished();
00780 }
00781 
00782 void kio_sieveProtocol::chmod(const KURL& url, int permissions)
00783 {
00784   switch ( permissions ) {
00785   case 0700: // activate
00786     activate(url);
00787     break;
00788   case 0600: // deactivate
00789     deactivate();
00790     break;
00791   default: // unsupported
00792     error(ERR_CANNOT_CHMOD, i18n("Cannot chmod to anything but 0700 (active) or 0600 (inactive script)."));
00793     return;
00794   }
00795 
00796   finished();
00797 }
00798 
00799 #if defined(_AIX) && defined(stat)
00800 #undef stat
00801 #endif
00802 
00803 void kio_sieveProtocol::stat(const KURL& url)
00804 {
00805     changeCheck( url );
00806     if (!connect())
00807         return;
00808 
00809     UDSEntry entry;
00810 
00811     QString filename = url.fileName(false);
00812 
00813     if (filename.isEmpty()) {
00814         UDSAtom atom;
00815         atom.m_uds = KIO::UDS_NAME;
00816         atom.m_str = "/";
00817         entry.append(atom);
00818 
00819         atom.m_uds = KIO::UDS_FILE_TYPE;
00820         atom.m_long = S_IFDIR;
00821         entry.append(atom);
00822 
00823         atom.m_uds = KIO::UDS_ACCESS;
00824         atom.m_long = 0700;
00825         entry.append(atom);
00826 
00827         statEntry(entry);
00828 
00829     } else {
00830         if (!sendData("LISTSCRIPTS"))
00831             return;
00832 
00833         while(receiveData()) {
00834             if (r.getType() == kio_sieveResponse::ACTION) {
00835                 if (r.getAction().contains("OK", false) == 1)
00836                     // Script list completed
00837                     break;
00838 
00839             } else
00840                 if (filename == QString::fromUtf8(r.getKey())) {
00841                     entry.clear();
00842 
00843                     UDSAtom atom;
00844                     atom.m_uds = KIO::UDS_NAME;
00845                     atom.m_str = QString::fromUtf8(r.getKey());
00846                     entry.append(atom);
00847 
00848                     atom.m_uds = KIO::UDS_FILE_TYPE;
00849                     atom.m_long = S_IFREG;
00850                     entry.append(atom);
00851 
00852                     atom.m_uds = KIO::UDS_ACCESS;
00853                     if ( r.getExtra() == "ACTIVE" )
00854                       atom.m_long = 0700; // mark exec'able
00855                     else
00856                       atom.m_long = 0600;
00857                     entry.append(atom);
00858 
00859                     atom.m_uds = KIO::UDS_MIME_TYPE;
00860                     atom.m_str = "application/sieve";
00861                     entry.append(atom);
00862 
00863                     //setMetaData("active", (r.getExtra() == "ACTIVE") ? "yes" : "no");
00864 
00865                     statEntry(entry);
00866                     // cannot break here because we need to clear
00867                     // the rest of the incoming data.
00868                 }
00869         }
00870     }
00871 
00872     finished();
00873 }
00874 
00875 void kio_sieveProtocol::listDir(const KURL& url)
00876 {
00877     changeCheck( url );
00878     if (!connect())
00879         return;
00880 
00881     if (!sendData("LISTSCRIPTS"))
00882         return;
00883 
00884     UDSEntry entry;
00885 
00886     while(receiveData()) {
00887         if (r.getType() == kio_sieveResponse::ACTION) {
00888             if (r.getAction().contains("OK", false) == 1)
00889                 // Script list completed.
00890                 break;
00891 
00892         } else {
00893             entry.clear();
00894 
00895             UDSAtom atom;
00896             atom.m_uds = KIO::UDS_NAME;
00897             atom.m_str = QString::fromUtf8(r.getKey());
00898             entry.append(atom);
00899 
00900             atom.m_uds = KIO::UDS_FILE_TYPE;
00901             atom.m_long = S_IFREG;
00902             entry.append(atom);
00903 
00904             atom.m_uds = KIO::UDS_ACCESS;
00905             if ( r.getExtra() == "ACTIVE" )
00906               atom.m_long = 0700; // mark exec'able
00907             else
00908               atom.m_long = 0600;
00909             entry.append(atom);
00910 
00911             atom.m_uds = KIO::UDS_MIME_TYPE;
00912             atom.m_str = "application/sieve";
00913             entry.append(atom);
00914 
00915             //asetMetaData("active", (r.getExtra() == "ACTIVE") ? "true" : "false");
00916 
00917             ksDebug() << "Listing script " << r.getKey() << endl;
00918             listEntry(entry , false);
00919         }
00920     }
00921 
00922     listEntry(entry, true);
00923 
00924     finished();
00925 }
00926 
00927 /* ---------------------------------------------------------------------------------- */
00928 bool kio_sieveProtocol::saslInteract( void *in, AuthInfo &ai )
00929 {
00930   ksDebug() << "sasl_interact" << endl;
00931   sasl_interact_t *interact = ( sasl_interact_t * ) in;
00932 
00933   //some mechanisms do not require username && pass, so it doesn't need a popup
00934   //window for getting this info
00935   for ( ; interact->id != SASL_CB_LIST_END; interact++ ) {
00936     if ( interact->id == SASL_CB_AUTHNAME ||
00937          interact->id == SASL_CB_PASS ) {
00938 
00939         if (m_sUser.isEmpty() || m_sPass.isEmpty()) {
00940             if (!openPassDlg(ai)) {
00941                 error(ERR_ABORTED, i18n("No authentication details supplied."));
00942                 return false;
00943             }
00944         m_sUser = ai.username;
00945         m_sPass = ai.password;
00946         }
00947       break;
00948     }
00949   }
00950 
00951   interact = ( sasl_interact_t * ) in;
00952   while( interact->id != SASL_CB_LIST_END ) {
00953     ksDebug() << "SASL_INTERACT id: " << interact->id << endl;
00954     switch( interact->id ) {
00955       case SASL_CB_USER:
00956       case SASL_CB_AUTHNAME:
00957         ksDebug() << "SASL_CB_[AUTHNAME|USER]: '" << m_sUser << "'" << endl;
00958         interact->result = strdup( m_sUser.utf8() );
00959         interact->len = strlen( (const char *) interact->result );
00960         break;
00961       case SASL_CB_PASS:
00962         ksDebug() << "SASL_CB_PASS: [hidden] " << endl;
00963         interact->result = strdup( m_sPass.utf8() );
00964         interact->len = strlen( (const char *) interact->result );
00965         break;
00966       default:
00967         interact->result = NULL; interact->len = 0;
00968         break;
00969     }
00970     interact++;
00971   }
00972   return true;
00973 }
00974 
00975 #define SASLERROR  error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occurred during authentication: %1").arg( \
00976       QString::fromUtf8( sasl_errdetail( conn ) )));
00977 
00978 bool kio_sieveProtocol::authenticate()
00979 {
00980   int result;
00981   sasl_conn_t *conn = NULL;
00982   sasl_interact_t *client_interact = NULL;
00983   const char *out = NULL;
00984   uint outlen;
00985   const char *mechusing = NULL;
00986     QByteArray challenge, tmp;
00987 
00988     /* Retrieve authentication details from user.
00989      * Note: should this require realm as well as user & pass details
00990      * before it automatically skips the prompt?
00991      * Note2: encoding issues with PLAIN login? */
00992     AuthInfo ai;
00993     ai.url.setProtocol("sieve");
00994     ai.url.setHost(m_sServer);
00995     ai.url.setPort(m_iPort);
00996     ai.username = m_sUser;
00997     ai.password = m_sPass;
00998     ai.keepPassword = true;
00999   ai.caption = i18n("Sieve Authentication Details");
01000   ai.comment = i18n("Please enter your authentication details for your sieve account "
01001       "(usually the same as your email password):");
01002 
01003   result = sasl_client_new( "sieve",
01004                        m_sServer.latin1(),
01005                        0, 0, callbacks, 0, &conn );
01006 
01007   if ( result != SASL_OK ) {
01008     ksDebug() << "sasl_client_new failed with: " << result << endl;
01009     SASLERROR
01010     return false;
01011   }
01012 
01013     QStringList strList;
01014 //  strList.append("NTLM");
01015 
01016   if ( !m_sAuth.isEmpty() )
01017     strList.append( m_sAuth );
01018   else
01019     strList = m_sasl_caps;
01020 
01021   do {
01022     result = sasl_client_start(conn, strList.join(" ").latin1(), &client_interact,
01023                        &out, &outlen, &mechusing);
01024 
01025     if (result == SASL_INTERACT)
01026       if ( !saslInteract( client_interact, ai ) ) {
01027         sasl_dispose( &conn );
01028         return false;
01029       };
01030   } while ( result == SASL_INTERACT );
01031 
01032   if ( result != SASL_CONTINUE && result != SASL_OK ) {
01033     ksDebug() << "sasl_client_start failed with: " << result << endl;
01034     SASLERROR
01035     sasl_dispose( &conn );
01036     return false;
01037   }
01038 
01039     ksDebug() << "Preferred authentication method is " << mechusing << "." << endl;
01040 
01041   QString firstCommand = "AUTHENTICATE \"" + QString::fromLatin1( mechusing ) + "\"";
01042   tmp.setRawData( out, outlen );
01043   KCodecs::base64Encode( tmp, challenge );
01044   tmp.resetRawData( out, outlen );
01045   if ( !challenge.isEmpty() ) {
01046     firstCommand += " \"";
01047     firstCommand += QString::fromLatin1( challenge.data(), challenge.size() );
01048     firstCommand += "\"";
01049   }
01050 
01051     if (!sendData( firstCommand.latin1() ))
01052         return false;
01053 
01054     QCString command;
01055 
01056     do {
01057         receiveData();
01058 
01059         if (operationResult() != OTHER)
01060             break;
01061 
01062         ksDebug() << "Challenge len  " << r.getQuantity() << endl;
01063 
01064         if (r.getType() != kio_sieveResponse::QUANTITY) {
01065       sasl_dispose( &conn );
01066             error(ERR_SLAVE_DEFINED,
01067                     i18n("A protocol error occurred during authentication.\n"
01068                             "Choose a different authentication method to %1.").arg(mechusing));
01069             return false;
01070         }
01071 
01072         uint qty = r.getQuantity();
01073 
01074         receiveData();
01075 
01076         if (r.getType() != kio_sieveResponse::ACTION && r.getAction().length() != qty) {
01077       sasl_dispose( &conn );
01078             error(ERR_UNSUPPORTED_PROTOCOL,
01079                     i18n("A protocol error occurred during authentication.\n"
01080                             "Choose a different authentication method to %1.").arg(mechusing));
01081             return false;
01082         }
01083 
01084     tmp.setRawData( r.getAction().data(), qty );
01085     KCodecs::base64Decode( tmp, challenge );
01086     tmp.resetRawData( r.getAction().data(), qty );
01087 //      ksDebug() << "S:  [" << r.getAction() << "]." << endl;
01088 //      ksDebug() << "S-1:  [" << QCString(challenge.data(), challenge.size()+1) << "]." << endl;
01089 
01090     do {
01091       result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(),
01092                                   challenge.size(),
01093                                   &client_interact,
01094                                   &out, &outlen);
01095 
01096       if (result == SASL_INTERACT)
01097         if ( !saslInteract( client_interact, ai ) ) {
01098           sasl_dispose( &conn );
01099           return false;
01100         };
01101     } while ( result == SASL_INTERACT );
01102 
01103     ksDebug() << "sasl_client_step: " << result << endl;
01104     if ( result != SASL_CONTINUE && result != SASL_OK ) {
01105       ksDebug() << "sasl_client_step failed with: " << result << endl;
01106       SASLERROR
01107       sasl_dispose( &conn );
01108       return false;
01109     }
01110 
01111     tmp.setRawData( out, outlen );
01112     KCodecs::base64Encode( tmp, challenge );
01113     tmp.resetRawData( out, outlen );
01114     sendData("\"" + QCString( challenge.data(), challenge.size()+1 ) + "\"");
01115 //    ksDebug() << "C:  [" << QCString(challenge.data(), challenge.size()+1) << "]." << endl;
01116 //    ksDebug() << "C-1:  [" << out << "]." << endl;
01117   } while ( true );
01118 
01119     ksDebug() << "Challenges finished." << endl;
01120   sasl_dispose( &conn );
01121 
01122     if (operationResult() == OK) {
01123         // Authentication succeeded.
01124         return true;
01125     } else {
01126         // Authentication failed.
01127         error(ERR_COULD_NOT_AUTHENTICATE, i18n("Authentication failed.\nMost likely the password is wrong.\nThe server responded:\n%1").arg( r.getAction() ) );
01128         return false;
01129     }
01130 }
01131 
01132 /* --------------------------------------------------------------------------- */
01133 void kio_sieveProtocol::mimetype(const KURL & url)
01134 {
01135     ksDebug() << "Requesting mimetype for " << url.prettyURL() << endl;
01136 
01137     if (url.fileName(false).isEmpty())
01138         mimeType( "inode/directory" );
01139     else
01140         mimeType( "application/sieve" );
01141 
01142     finished();
01143 }
01144 
01145 
01146 /* --------------------------------------------------------------------------- */
01147 bool kio_sieveProtocol::sendData(const QCString &data)
01148 {
01149     QCString write_buf = data + "\r\n";
01150 
01151     //ksDebug() << "C: " << data << endl;
01152 
01153     // Write the command
01154     ssize_t write_buf_len = write_buf.length();
01155     if (write(write_buf.data(), write_buf_len) != write_buf_len) {
01156         error(ERR_COULD_NOT_WRITE, i18n("Network error."));
01157         disconnect(true);
01158         return false;
01159     }
01160 
01161     return true;
01162 }
01163 
01164 /* --------------------------------------------------------------------------- */
01165 bool kio_sieveProtocol::receiveData(bool waitForData, QCString *reparse)
01166 {
01167     QCString interpret;
01168     int start, end;
01169 
01170     if (!reparse) {
01171         if (!waitForData)
01172             // is there data waiting?
01173             if (atEnd()) return false;
01174 
01175         // read data from the server
01176         char buffer[SIEVE_DEFAULT_RECIEVE_BUFFER];
01177         readLine(buffer, SIEVE_DEFAULT_RECIEVE_BUFFER - 1);
01178         buffer[SIEVE_DEFAULT_RECIEVE_BUFFER-1] = '\0';
01179 
01180         // strip LF/CR
01181         interpret = QCString(buffer).left(qstrlen(buffer) - 2);
01182 
01183     } else {
01184         interpret = reparse->copy();
01185     }
01186 
01187     r.clear();
01188 
01189     //ksDebug() << "S: " << interpret << endl;
01190 
01191     switch(interpret[0]) {
01192         case '{':
01193           {
01194             // expecting {quantity}
01195             start = 0;
01196             end = interpret.find("+}", start + 1);
01197             // some older versions of Cyrus enclose the literal size just in { } instead of { +}
01198             if ( end == -1 )
01199                 end = interpret.find('}', start + 1);
01200 
01201             bool ok = false;
01202             r.setQuantity(interpret.mid(start + 1, end - start - 1).toUInt( &ok ));
01203             if (!ok) {
01204                 disconnect();
01205                 error(ERR_INTERNAL_SERVER, i18n("A protocol error occurred."));
01206                 return false;
01207             }
01208 
01209             return true;
01210           }
01211         case '"':
01212             // expecting "key" "value" pairs
01213             break;
01214         default:
01215             // expecting single string
01216             r.setAction(interpret);
01217             return true;
01218     }
01219 
01220     start = 0;
01221 
01222     end = interpret.find(34, start + 1);
01223     if (end == -1) {
01224         ksDebug() << "Possible insufficient buffer size." << endl;
01225         r.setKey(interpret.right(interpret.length() - start));
01226         return true;
01227     }
01228 
01229     r.setKey(interpret.mid(start + 1, end - start - 1));
01230 
01231     start = interpret.find(34, end + 1);
01232     if (start == -1) {
01233         if ((int)interpret.length() > end)
01234             // skip " and space
01235             r.setExtra(interpret.right(interpret.length() - end - 2));
01236 
01237         return true;
01238     }
01239 
01240     end = interpret.find(34, start + 1);
01241     if (end == -1) {
01242         ksDebug() << "Possible insufficient buffer size." << endl;
01243         r.setVal(interpret.right(interpret.length() - start));
01244         return true;
01245     }
01246 
01247     r.setVal(interpret.mid(start + 1, end - start - 1));
01248     return true;
01249 }
01250 
01251 bool kio_sieveProtocol::operationSuccessful()
01252 {
01253     while (receiveData(false)) {
01254         if (r.getType() == kio_sieveResponse::ACTION) {
01255             QCString response = r.getAction().left(2);
01256             if (response == "OK") {
01257                 return true;
01258             } else if (response == "NO") {
01259                 return false;
01260             }
01261         }
01262     }
01263     return false;
01264 }
01265 
01266 int kio_sieveProtocol::operationResult()
01267 {
01268     if (r.getType() == kio_sieveResponse::ACTION) {
01269         QCString response = r.getAction().left(2);
01270         if (response == "OK") {
01271             return OK;
01272         } else if (response == "NO") {
01273             return NO;
01274         } else if (response == "BY"/*E*/) {
01275             return BYE;
01276         }
01277     }
01278 
01279     return OTHER;
01280 }
01281 
01282 bool kio_sieveProtocol::requestCapabilitiesAfterStartTLS() const
01283 {
01284   // Cyrus didn't send CAPABILITIES after STARTTLS until 2.3.11, which is
01285   // not standard conform, but we need to support that anyway.
01286   // m_implementation looks like this 'Cyrus timsieved v2.2.12' for Cyrus btw.
01287   QRegExp regExp( "Cyrus\\stimsieved\\sv(\\d+)\\.(\\d+)\\.(\\d+)([-\\w]*)", false );
01288   if ( regExp.search( m_implementation ) >= 0 ) {
01289     const int major = regExp.cap( 1 ).toInt();
01290     const int minor = regExp.cap( 2 ).toInt();
01291     const int patch = regExp.cap( 3 ).toInt();
01292     const QString vendor = regExp.cap( 4 );
01293     if ( major < 2 || (major == 2 && (minor < 3 || (minor == 3 && patch < 11))) || (vendor == "-kolab-nocaps") ) {
01294       ksDebug() << k_funcinfo << "Enabling compat mode for Cyrus < 2.3.11 or Cyrus marked as \"kolab-nocaps\"" << endl;
01295       return true;
01296     }
01297   }
01298   return false;
01299 }