• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • KDE Home
  • Contact Us
 

kioslave/nntp

nntp.cpp

00001 /* This file is part of KDE
00002    Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de>
00003    Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com>
00004    Copyright (C) 2005 by Volker Krause <vkrause@kde.org>
00005 
00006    This is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public
00008    License version 2 as published by the Free Software Foundation.
00009 */
00010 
00011 #include "nntp.h"
00012 
00013 #include <sys/stat.h>
00014 #include <stdlib.h>
00015 #include <stdio.h>
00016 
00017 #include <QDir>
00018 #include <QHash>
00019 #include <QRegExp>
00020 
00021 #include <kcomponentdata.h>
00022 #include <kdebug.h>
00023 #include <kglobal.h>
00024 #include <klocale.h>
00025 
00026 #include <kio/ioslave_defaults.h>
00027 
00028 #define DBG_AREA 7114
00029 #define DBG kDebug(DBG_AREA)
00030 
00031 #undef ERR
00032 #define ERR kError(DBG_AREA)
00033 
00034 using namespace KIO;
00035 
00036 extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
00037 
00038 int kdemain(int argc, char **argv) {
00039 
00040   KComponentData componentData("kio_nntp");
00041   if (argc != 4) {
00042     fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n");
00043     exit(-1);
00044   }
00045 
00046   NNTPProtocol *slave;
00047 
00048   // Are we going to use SSL?
00049   if (strcasecmp(argv[1], "nntps") == 0) {
00050     slave = new NNTPProtocol(argv[2], argv[3], true);
00051   } else {
00052     slave = new NNTPProtocol(argv[2], argv[3], false);
00053   }
00054 
00055   slave->dispatchLoop();
00056   delete slave;
00057 
00058   return 0;
00059 }
00060 
00061 /****************** NNTPProtocol ************************/
00062 
00063 NNTPProtocol::NNTPProtocol ( const QByteArray & pool, const QByteArray & app, bool isSSL )
00064   : TCPSlaveBase((isSSL ? "nntps" : "nntp"), pool, app, isSSL ),
00065     isAuthenticated( false )
00066 {
00067   DBG << "=============> NNTPProtocol::NNTPProtocol";
00068 
00069   readBufferLen = 0;
00070   m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT;
00071   m_port = m_defaultPort;
00072 }
00073 
00074 NNTPProtocol::~NNTPProtocol() {
00075   DBG << "<============= NNTPProtocol::~NNTPProtocol";
00076 
00077   // close connection
00078   nntp_close();
00079 }
00080 
00081 void NNTPProtocol::setHost ( const QString & host, quint16 port, const QString & user,
00082                              const QString & pass )
00083 {
00084   DBG << ( ! user.isEmpty() ? (user+'@') : QString(""))
00085       << host << ":" << ( ( port == 0 ) ? m_defaultPort : port  );
00086 
00087   if ( isConnected() && (mHost != host || m_port != port ||
00088        mUser != user || mPass != pass) )
00089     nntp_close();
00090 
00091   mHost = host;
00092   m_port = ( ( port == 0 ) ? m_defaultPort : port );
00093   mUser = user;
00094   mPass = pass;
00095 }
00096 
00097 void NNTPProtocol::get( const KUrl& url )
00098 {
00099   DBG << url.prettyUrl();
00100   QString path = QDir::cleanPath(url.path());
00101 
00102   // path should be like: /group/<msg_id> or /group/<serial number>
00103   if ( path.startsWith( '/' ) )
00104     path.remove( 0, 1 );
00105   int pos = path.indexOf( '/' );
00106   QString group;
00107   QString msg_id;
00108   if ( pos > 0 ) {
00109     group = path.left( pos );
00110     msg_id = path.mid( pos + 1 );
00111   }
00112 
00113   if ( group.isEmpty() || msg_id.isEmpty() ) {
00114     error(ERR_DOES_NOT_EXIST,path);
00115     return;
00116   }
00117 
00118   int res_code;
00119   DBG << "group:" << group << "msg:" << msg_id;
00120 
00121   if ( !nntp_open() )
00122     return;
00123 
00124   // select group if necessary
00125   if ( mCurrentGroup != group && !group.isEmpty() ) {
00126     infoMessage( i18n("Selecting group %1...", group ) );
00127     res_code = sendCommand( "GROUP " + group );
00128     if ( res_code == 411 ){
00129       error( ERR_DOES_NOT_EXIST, path );
00130       mCurrentGroup.clear();
00131       return;
00132     } else if ( res_code != 211 ) {
00133       unexpected_response( res_code, "GROUP" );
00134       mCurrentGroup.clear();
00135       return;
00136     }
00137     mCurrentGroup = group;
00138   }
00139 
00140   // get article
00141   infoMessage( i18n("Downloading article...") );
00142   res_code = sendCommand( "ARTICLE " + msg_id );
00143   if ( res_code == 423 || res_code == 430 ) {
00144     error( ERR_DOES_NOT_EXIST, path );
00145     return;
00146   } else if (res_code != 220) {
00147     unexpected_response(res_code,"ARTICLE");
00148     return;
00149   }
00150 
00151   // read and send data
00152   char tmp[MAX_PACKET_LEN];
00153   while ( true ) {
00154     if ( !waitForResponse( readTimeout() ) ) {
00155       error( ERR_SERVER_TIMEOUT, mHost );
00156       nntp_close();
00157       return;
00158     }
00159     int len = readLine( tmp, MAX_PACKET_LEN );
00160     const char* buffer = tmp;
00161     if ( len <= 0 )
00162       break;
00163     if ( len == 3 && tmp[0] == '.' && tmp[1] == '\r' && tmp[2] == '\n')
00164       break;
00165     if ( len > 1 && tmp[0] == '.' && tmp[1] == '.' ) {
00166       ++buffer;
00167       --len;
00168     }
00169     data( QByteArray::fromRawData( buffer, len ) );
00170   }
00171 
00172   // end of data
00173   data(QByteArray());
00174 
00175   // finish
00176   finished();
00177 }
00178 
00179 void NNTPProtocol::put( const KUrl &/*url*/, int /*permissions*/, KIO::JobFlags /*flags*/ )
00180 {
00181   if ( !nntp_open() )
00182     return;
00183   if ( post_article() )
00184     finished();
00185 }
00186 
00187 void NNTPProtocol::special(const QByteArray& data) {
00188   // 1 = post article
00189   int cmd;
00190   QDataStream stream(data);
00191 
00192   if ( !nntp_open() )
00193     return;
00194 
00195   stream >> cmd;
00196   if (cmd == 1) {
00197     if (post_article()) finished();
00198   } else {
00199     error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1", cmd));
00200   }
00201 }
00202 
00203 bool NNTPProtocol::post_article() {
00204   DBG;
00205 
00206   // send post command
00207   infoMessage( i18n("Sending article...") );
00208   int res_code = sendCommand( "POST" );
00209   if (res_code == 440) { // posting not allowed
00210     error(ERR_WRITE_ACCESS_DENIED, mHost);
00211     return false;
00212   } else if (res_code != 340) { // 340: ok, send article
00213     unexpected_response(res_code,"POST");
00214     return false;
00215   }
00216 
00217   // send article now
00218   int result;
00219   bool last_chunk_had_line_ending = true;
00220   do {
00221     QByteArray buffer;
00222     dataReq();
00223     result = readData( buffer );
00224     DBG << "receiving data:" << buffer;
00225     // treat the buffer data
00226     if ( result > 0 ) {
00227       // translate "\r\n." to "\r\n.."
00228       int pos = 0;
00229       if ( last_chunk_had_line_ending && buffer[0] == '.' ) {
00230         buffer.insert( 0, '.' );
00231         pos += 2;
00232       }
00233       last_chunk_had_line_ending = ( buffer.endsWith( "\r\n" ) ); //krazy:exclude=strings
00234       while ( (pos = buffer.indexOf( "\r\n.", pos )) > 0) {
00235         buffer.insert( pos + 2, '.' );
00236         pos += 4;
00237       }
00238 
00239       // send data to socket, write() doesn't send the terminating 0
00240       write( buffer, buffer.length() );
00241       DBG << "writing:" << buffer;
00242     }
00243   } while ( result > 0 );
00244 
00245   // error occurred?
00246   if (result<0) {
00247     ERR << "error while getting article data for posting";
00248     nntp_close();
00249     return false;
00250   }
00251 
00252   // send end mark
00253   write( "\r\n.\r\n", 5 );
00254 
00255   // get answer
00256   res_code = evalResponse( readBuffer, readBufferLen );
00257   if (res_code == 441) { // posting failed
00258     error(ERR_COULD_NOT_WRITE, mHost);
00259     return false;
00260   } else if (res_code != 240) {
00261     unexpected_response(res_code,"POST");
00262     return false;
00263   }
00264 
00265   return true;
00266 }
00267 
00268 
00269 void NNTPProtocol::stat( const KUrl& url ) {
00270   DBG << url.prettyUrl();
00271   UDSEntry entry;
00272   QString path = QDir::cleanPath(url.path());
00273   QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive);
00274   QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive);
00275   int pos;
00276   QString group;
00277   QString msg_id;
00278 
00279   // / = group list
00280   if (path.isEmpty() || path == "/") {
00281     DBG << "root";
00282     fillUDSEntry( entry, QString(), 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
00283 
00284   // /group = message list
00285   } else if (regGroup.indexIn(path) == 0) {
00286     if ( path.startsWith( '/' ) ) path.remove(0,1);
00287     if ((pos = path.indexOf('/')) > 0) group = path.left(pos);
00288     else group = path;
00289     DBG << "group:" << group;
00290     // postingAllowed should be ored here with "group not moderated" flag
00291     // as size the num of messages (GROUP cmd) could be given
00292     fillUDSEntry( entry, group, 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
00293 
00294   // /group/<msg_id> = message
00295   } else if (regMsgId.indexIn(path) == 0) {
00296     pos = path.indexOf('<');
00297     group = path.left(pos);
00298     msg_id = KUrl::fromPercentEncoding( path.right(path.length()-pos).toLatin1() );
00299     if ( group.startsWith( '/' ) )
00300       group.remove( 0, 1 );
00301     if ((pos = group.indexOf('/')) > 0) group = group.left(pos);
00302     DBG << "group:" << group << "msg:" << msg_id;
00303     fillUDSEntry( entry, msg_id, 0, true );
00304 
00305   // invalid url
00306   } else {
00307     error(ERR_DOES_NOT_EXIST,path);
00308     return;
00309   }
00310 
00311   statEntry(entry);
00312   finished();
00313 }
00314 
00315 void NNTPProtocol::listDir( const KUrl& url ) {
00316   DBG << url.prettyUrl();
00317   if ( !nntp_open() )
00318     return;
00319 
00320   QString path = QDir::cleanPath(url.path());
00321 
00322   if (path.isEmpty())
00323   {
00324     KUrl newURL(url);
00325     newURL.setPath("/");
00326     DBG << "redirecting to" << newURL.prettyUrl();
00327     redirection(newURL);
00328     finished();
00329     return;
00330   }
00331   else if ( path == "/" ) {
00332     fetchGroups( url.queryItem( "since" ), url.queryItem( "desc" ) == "true" );
00333     finished();
00334   } else {
00335     // if path = /group
00336     int pos;
00337     QString group;
00338     if ( path.startsWith( '/' ) )
00339       path.remove( 0, 1 );
00340     if ((pos = path.indexOf('/')) > 0)
00341       group = path.left(pos);
00342     else
00343       group = path;
00344     QString first = url.queryItem( "first" );
00345     QString max = url.queryItem( "max" );
00346     if ( fetchGroup( group, first.toULong(), max.toULong() ) )
00347       finished();
00348   }
00349 }
00350 
00351 void NNTPProtocol::fetchGroups( const QString &since, bool desc )
00352 {
00353   int expected;
00354   int res;
00355   if ( since.isEmpty() ) {
00356     // full listing
00357     infoMessage( i18n("Downloading group list...") );
00358     res = sendCommand( "LIST" );
00359     expected = 215;
00360   } else {
00361     // incremental listing
00362     infoMessage( i18n("Looking for new groups...") );
00363     res = sendCommand( "NEWGROUPS " + since );
00364     expected = 231;
00365   }
00366   if ( res != expected ) {
00367     unexpected_response( res, "LIST" );
00368     return;
00369   }
00370 
00371   // read newsgroups line by line
00372   QByteArray line;
00373   QString group;
00374   int pos, pos2;
00375   long msg_cnt;
00376   long access;
00377   UDSEntry entry;
00378   QHash<QString, UDSEntry> entryMap;
00379 
00380   // read in data and process each group. one line at a time
00381   while ( true ) {
00382     if ( ! waitForResponse( readTimeout() ) ) {
00383       error( ERR_SERVER_TIMEOUT, mHost );
00384       nntp_close();
00385       return;
00386     }
00387     readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
00388     line = QByteArray( readBuffer, readBufferLen );
00389     if ( line == ".\r\n" )
00390       break;
00391 
00392     // group name
00393     if ((pos = line.indexOf(' ')) > 0) {
00394 
00395       group = line.left(pos);
00396 
00397       // number of messages
00398       line.remove(0,pos+1);
00399       long last = 0;
00400       access = 0;
00401       if (((pos = line.indexOf(' ')) > 0 || (pos = line.indexOf('\t')) > 0) &&
00402           ((pos2 = line.indexOf(' ',pos+1)) > 0 || (pos2 = line.indexOf('\t',pos+1)) > 0)) {
00403         last = line.left(pos).toLongLong();
00404         long first = line.mid(pos+1,pos2-pos-1).toLongLong();
00405         msg_cnt = abs(last-first+1);
00406         // group access rights
00407         switch ( line[pos2 + 1] ) {
00408           case 'n': access = 0; break;
00409           case 'm': access = S_IWUSR | S_IWGRP; break;
00410           case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH; break;
00411         }
00412       } else {
00413         msg_cnt = 0;
00414       }
00415 
00416       entry.clear();
00417       fillUDSEntry( entry, group, msg_cnt, false, access );
00418       if ( !desc )
00419         listEntry( entry, false );
00420       else
00421         entryMap.insert( group, entry );
00422     }
00423   }
00424 
00425   // handle group descriptions
00426   QHash<QString, UDSEntry>::Iterator it = entryMap.begin();
00427   if ( desc ) {
00428     infoMessage( i18n("Downloading group descriptions...") );
00429     totalSize( entryMap.size() );
00430   }
00431   while ( desc ) {
00432     // request all group descriptions
00433     if ( since.isEmpty() )
00434       res = sendCommand( "LIST NEWSGROUPS" );
00435     else {
00436       // request only descriptions for new groups
00437       if ( it == entryMap.end() )
00438         break;
00439       res = sendCommand( "LIST NEWSGROUPS " + it.key() );
00440       ++it;
00441       if( res == 503 ) {
00442         // Information not available (RFC 2980 ยง2.1.6), try next group
00443         continue;
00444       }
00445     }
00446     if ( res != 215 ) {
00447       // No group description available or not implemented
00448       break;
00449     }
00450 
00451     // download group descriptions
00452     while ( true ) {
00453       if ( ! waitForResponse( readTimeout() ) ) {
00454         error( ERR_SERVER_TIMEOUT, mHost );
00455         nntp_close();
00456         return;
00457       }
00458       readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
00459       line = QByteArray( readBuffer, readBufferLen );
00460       if ( line == ".\r\n" )
00461         break;
00462 
00463       //DBG << "  fetching group description: " << QString( line ).trimmed();
00464       int pos = line.indexOf( ' ' );
00465       pos = pos < 0 ? line.indexOf( '\t' ) : qMin( pos, line.indexOf( '\t' ) );
00466       group = line.left( pos );
00467       QString groupDesc = line.right( line.length() - pos ).trimmed();
00468 
00469       if ( entryMap.contains( group ) ) {
00470         entry = entryMap.take( group );
00471         entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc );
00472         listEntry( entry, false );
00473       }
00474     }
00475 
00476     if ( since.isEmpty() )
00477       break;
00478   }
00479   // take care of groups without descriptions
00480   for ( QHash<QString, UDSEntry>::Iterator it = entryMap.begin(); it != entryMap.end(); ++it )
00481     listEntry( it.value(), false );
00482 
00483   entry.clear();
00484   listEntry( entry, true );
00485 }
00486 
00487 bool NNTPProtocol::fetchGroup( QString &group, unsigned long first, unsigned long max ) {
00488   int res_code;
00489   QString resp_line;
00490 
00491   // select group
00492   infoMessage( i18n("Selecting group %1...", group ) );
00493   res_code = sendCommand( "GROUP " + group );
00494   if ( res_code == 411 ) {
00495     error( ERR_DOES_NOT_EXIST, group );
00496     mCurrentGroup.clear();
00497     return false;
00498   } else if ( res_code != 211 ) {
00499     unexpected_response( res_code, "GROUP" );
00500     mCurrentGroup.clear();
00501     return false;
00502   }
00503   mCurrentGroup = group;
00504 
00505   // repsonse to "GROUP <requested-group>" command is 211 then find the message count (cnt)
00506   // and the first and last message followed by the group name
00507   unsigned long firstSerNum, lastSerNum;
00508   resp_line = QString::fromLatin1( readBuffer );
00509   QRegExp re ( "211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)");
00510   if ( re.indexIn( resp_line ) != -1 ) {
00511     firstSerNum = re.cap( 2 ).toLong();
00512     lastSerNum = re.cap( 3 ).toLong();
00513   } else {
00514     error( ERR_INTERNAL, i18n("Could not extract message serial numbers from server response:\n%1",
00515        resp_line ) );
00516     return false;
00517   }
00518 
00519   if (firstSerNum == 0)
00520     return true;
00521   first = qMax( first, firstSerNum );
00522   if ( lastSerNum < first ) { // No need to fetch anything
00523     // note: this also ensure that "lastSerNum - first" is not negative
00524     // in the next test (in "unsigned long" computation this leads to an overflow
00525     return true;
00526   }
00527   if ( max > 0 && lastSerNum - first > max )
00528     first = lastSerNum - max + 1;
00529 
00530   DBG << "Starting from serial number: " << first << " of " << firstSerNum << " - " << lastSerNum;
00531   setMetaData( "FirstSerialNumber", QString::number( firstSerNum ) );
00532   setMetaData( "LastSerialNumber", QString::number( lastSerNum ) );
00533 
00534   infoMessage( i18n("Downloading new headers...") );
00535   totalSize( lastSerNum - first );
00536   bool notSupported = true;
00537   if ( fetchGroupXOVER( first, notSupported ) )
00538     return true;
00539   else if ( notSupported )
00540     return fetchGroupRFC977( first );
00541   return false;
00542 }
00543 
00544 
00545 bool NNTPProtocol::fetchGroupRFC977( unsigned long first )
00546 {
00547   UDSEntry entry;
00548 
00549   // set article pointer to first article and get msg-id of it
00550   int res_code = sendCommand( "STAT " + QString::number( first ) );
00551   QString resp_line = readBuffer;
00552   if (res_code != 223) {
00553     unexpected_response(res_code,"STAT");
00554     return false;
00555   }
00556 
00557   //STAT res_line: 223 nnn <msg_id> ...
00558   QString msg_id;
00559   int pos, pos2;
00560   if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) {
00561     msg_id = resp_line.mid(pos,pos2-pos+1);
00562     fillUDSEntry( entry, msg_id, 0, true );
00563     listEntry( entry, false );
00564   } else {
00565     error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1",
00566       resp_line));
00567     return false;
00568   }
00569 
00570   // go through all articles
00571   while (true) {
00572     res_code = sendCommand("NEXT");
00573     if (res_code == 421) {
00574       // last artice reached
00575       entry.clear();
00576       listEntry( entry, true );
00577       return true;
00578     } else if (res_code != 223) {
00579       unexpected_response(res_code,"NEXT");
00580       return false;
00581     }
00582 
00583     //res_line: 223 nnn <msg_id> ...
00584     resp_line = readBuffer;
00585     if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) {
00586       msg_id = resp_line.mid(pos,pos2-pos+1);
00587       entry.clear();
00588       fillUDSEntry( entry, msg_id, 0, true );
00589       listEntry( entry, false );
00590     } else {
00591       error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1",
00592         resp_line));
00593       return false;
00594     }
00595   }
00596   return true; // Not reached
00597 }
00598 
00599 
00600 bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool &notSupported )
00601 {
00602   notSupported = false;
00603 
00604   QString line;
00605   QStringList headers;
00606 
00607   int res = sendCommand( "LIST OVERVIEW.FMT" );
00608   if ( res == 215 ) {
00609     while ( true ) {
00610       if ( ! waitForResponse( readTimeout() ) ) {
00611         error( ERR_SERVER_TIMEOUT, mHost );
00612         nntp_close();
00613         return false;
00614       }
00615       readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
00616       line = QString::fromLatin1( readBuffer, readBufferLen );
00617       if ( line == ".\r\n" )
00618         break;
00619       headers << line.trimmed();
00620       DBG << "OVERVIEW.FMT:" << line.trimmed();
00621     }
00622   } else {
00623     // fallback to defaults
00624     headers << "Subject:" << "From:" << "Date:" << "Message-ID:"
00625         << "References:" << "Bytes:" << "Lines:";
00626   }
00627 
00628   res = sendCommand( "XOVER " + QString::number( first ) + '-' );
00629   if ( res == 420 )
00630     return true; // no articles selected
00631   if ( res == 500 )
00632     notSupported = true; // unknwon command
00633   if ( res != 224 ) {
00634     unexpected_response( res, "XOVER" );
00635     return false;
00636   }
00637 
00638   long msgSize;
00639   QString name;
00640   UDSEntry entry;
00641   int udsType;
00642 
00643   QStringList fields;
00644   while ( true ) {
00645     if ( ! waitForResponse( readTimeout() ) ) {
00646       error( ERR_SERVER_TIMEOUT, mHost );
00647       nntp_close();
00648       return false;
00649     }
00650     readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
00651     line = QString::fromLatin1( readBuffer, readBufferLen );
00652     if ( line == ".\r\n" ) {
00653       entry.clear();
00654       listEntry( entry, true );
00655       return true;
00656     }
00657 
00658     fields = line.split( '\t', QString::KeepEmptyParts);
00659     msgSize = 0;
00660     entry.clear();
00661     udsType = KIO::UDSEntry::UDS_EXTRA;
00662     QStringList::ConstIterator it = headers.constBegin();
00663     QStringList::ConstIterator it2 = fields.constBegin();
00664     // first entry is the serial number
00665     name = (*it2);
00666     ++it2;
00667     for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) {
00668       if ( (*it) == "Bytes:" ) {
00669         msgSize = (*it2).toLong();
00670         continue;
00671       }
00672       QString atomStr;
00673       if ( (*it).endsWith( QLatin1String( "full" ) ) )
00674         if ( (*it2).trimmed().isEmpty() )
00675           atomStr = (*it).left( (*it).indexOf( ':' ) + 1 ); // strip of the 'full' suffix
00676         else
00677           atomStr = (*it2).trimmed();
00678       else
00679         atomStr = (*it) + ' ' + (*it2).trimmed();
00680       entry.insert( udsType++, atomStr );
00681       if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END )
00682         break;
00683     }
00684     fillUDSEntry( entry, name, msgSize, true );
00685     listEntry( entry, false );
00686   }
00687   return true; // not reached
00688 }
00689 
00690 
00691 void NNTPProtocol::fillUDSEntry( UDSEntry& entry, const QString& name, long size,
00692   bool is_article, long access ) {
00693 
00694   long posting=0;
00695 
00696   // entry name
00697   entry.insert(KIO::UDSEntry::UDS_NAME, name);
00698 
00699   // entry size
00700   entry.insert(KIO::UDSEntry::UDS_SIZE, size);
00701 
00702   // file type
00703   entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR);
00704 
00705   // access permissions
00706   posting = postingAllowed? access : 0;
00707   long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) :
00708     (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting);
00709   entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal);
00710 
00711   entry.insert(KIO::UDSEntry::UDS_USER, mUser.isEmpty() ? QString::fromLatin1("root") : mUser);
00712 
00713   /*
00714   entry->insert(UDS_GROUP, QString::fromLatin1("root"));
00715   */
00716 
00717   // MIME type
00718   if (is_article) {
00719     entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/news") );
00720   }
00721 }
00722 
00723 void NNTPProtocol::nntp_close () {
00724   if ( isConnected() ) {
00725     write( "QUIT\r\n", 6 );
00726     disconnectFromHost();
00727     isAuthenticated = false;
00728   }
00729   mCurrentGroup.clear();
00730 }
00731 
00732 bool NNTPProtocol::nntp_open()
00733 {
00734   // if still connected reuse connection
00735   if ( isConnected() ) {
00736     DBG << "reusing old connection";
00737     return true;
00738   }
00739 
00740   DBG << "  nntp_open -- creating a new connection to" << mHost << ":" << m_port;
00741   // create a new connection (connectToHost() includes error handling)
00742   infoMessage( i18n("Connecting to server...") );
00743   if ( connectToHost( (isAutoSsl() ? "nntps" : "nntp"), mHost, m_port ) )
00744   {
00745     DBG << "  nntp_open -- connection is open";
00746 
00747     // read greeting
00748     int res_code = evalResponse( readBuffer, readBufferLen );
00749 
00750     /* expect one of
00751          200 server ready - posting allowed
00752          201 server ready - no posting allowed
00753     */
00754     if ( ! ( res_code == 200 || res_code == 201 ) )
00755     {
00756       unexpected_response(res_code,"CONNECT");
00757       return false;
00758     }
00759 
00760     DBG << "  nntp_open -- greating was read res_code :" << res_code;
00761 
00762     res_code = sendCommand("MODE READER");
00763 
00764     // TODO: not in RFC 977, so we should not abort here
00765     if ( !(res_code == 200 || res_code == 201) ) {
00766       unexpected_response( res_code, "MODE READER" );
00767       return false;
00768     }
00769 
00770     // let local class know whether posting is allowed or not
00771     postingAllowed = (res_code == 200);
00772 
00773     // activate TLS if requested
00774     if ( metaData("tls") == "on" ) {
00775       if ( sendCommand( "STARTTLS" ) != 382 ) {
00776         error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") );
00777         return false;
00778       }
00779       if ( !startSsl() ) {
00780         error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") );
00781         return false;
00782       }
00783     }
00784 
00785     // *try* to authenticate now (see bug#167718)
00786     authenticate();
00787 
00788     return true;
00789   }
00790 
00791   return false;
00792 }
00793 
00794 int NNTPProtocol::sendCommand( const QString &cmd )
00795 {
00796   int res_code = 0;
00797 
00798   if ( !nntp_open() ) {
00799     ERR << "NOT CONNECTED, cannot send cmd" << cmd;
00800     return 0;
00801   }
00802 
00803   DBG << "cmd:" << cmd;
00804 
00805   write( cmd.toLatin1(), cmd.length() );
00806   // check the command for proper termination
00807   if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) )
00808     write( "\r\n", 2 );
00809   res_code =  evalResponse( readBuffer, readBufferLen );
00810 
00811   // if authorization needed send user info
00812   if (res_code == 480) {
00813     DBG << "auth needed, sending user info";
00814 
00815     if ( mUser.isEmpty() || mPass.isEmpty() ) {
00816       KIO::AuthInfo authInfo;
00817       authInfo.username = mUser;
00818       authInfo.password = mPass;
00819       if ( openPasswordDialog( authInfo ) ) {
00820         mUser = authInfo.username;
00821         mPass = authInfo.password;
00822       }
00823     }
00824     if ( mUser.isEmpty() || mPass.isEmpty() )
00825       return res_code;
00826 
00827     res_code = authenticate();
00828     if (res_code != 281) {
00829       // error should be handled by invoking function
00830       return res_code;
00831     }
00832 
00833     // ok now, resend command
00834     write( cmd.toLatin1(), cmd.length() );
00835     if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) )
00836       write( "\r\n", 2 );
00837     res_code = evalResponse( readBuffer, readBufferLen );
00838   }
00839 
00840   return res_code;
00841 }
00842 
00843 int NNTPProtocol::authenticate()
00844 {
00845   int res_code = 0;
00846 
00847   if( isAuthenticated ) {
00848     // already authenticated
00849     return 281;
00850   }
00851 
00852   if( mUser.isEmpty() || mPass.isEmpty() ) {
00853     return 281; // failsafe : maybe add a "relax" mode to optionally ask user/pwd.
00854   }
00855 
00856   // send username to server and confirm response
00857   write( "AUTHINFO USER ", 14 );
00858   write( mUser.toLatin1(), mUser.length() );
00859   write( "\r\n", 2 );
00860   res_code = evalResponse( readBuffer, readBufferLen );
00861 
00862   if( res_code == 281 ) {
00863     // no password needed (RFC 2980 3.1.1 does not required one)
00864     return res_code;
00865   }
00866   if (res_code != 381) {
00867     // error should be handled by invoking function
00868     return res_code;
00869   }
00870 
00871   // send password
00872   write( "AUTHINFO PASS ", 14 );
00873   write( mPass.toLatin1(), mPass.length() );
00874   write( "\r\n", 2 );
00875   res_code = evalResponse( readBuffer, readBufferLen );
00876 
00877   if( res_code == 281 ) {
00878     isAuthenticated = true;
00879   }
00880 
00881   return res_code;
00882 }
00883 
00884 void NNTPProtocol::unexpected_response( int res_code, const QString &command )
00885 {
00886   ERR << "Unexpected response to" << command << "command: (" << res_code << ")"
00887       << readBuffer;
00888 
00889   // See RFC 3977 appendix C "Summary of Response Codes"
00890   switch ( res_code ) {
00891     case 205: // connection closed by the server: this can happens, e.g. if the session timeout on the server side
00892       // Not the same thing, but use the same message as code 400 anyway.
00893     case 400: // temporary issue on the server
00894       error( ERR_INTERNAL_SERVER,
00895              i18n( "The server %1 could not handle your request.\n"
00896                    "Please try again now, or later if the problem persists.", mHost ) );
00897       break;
00898     case 480: // credential request
00899       error( ERR_COULD_NOT_LOGIN,
00900              i18n( "You need to authenticate to access the requested resource." ) );
00901     case 481: // wrong credential (TODO: place a specific message for this case)
00902       error( ERR_COULD_NOT_LOGIN,
00903              i18n( "The supplied login and/or password are incorrect." ) );
00904       break;
00905     case 502:
00906       error( ERR_ACCESS_DENIED, mHost );
00907       break;
00908     default:
00909       error( ERR_INTERNAL, i18n( "Unexpected server response to %1 command:\n%2", command, readBuffer ) );
00910   }
00911 
00912   nntp_close();
00913 }
00914 
00915 int NNTPProtocol::evalResponse ( char *data, ssize_t &len )
00916 {
00917   if ( !waitForResponse( responseTimeout() ) ) {
00918     error( ERR_SERVER_TIMEOUT , mHost );
00919     nntp_close();
00920     return -1;
00921   }
00922   len = readLine( data, MAX_PACKET_LEN );
00923 
00924   if ( len < 3 )
00925     return -1;
00926 
00927   // get the first three characters. should be the response code
00928   int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) );
00929 
00930   DBG << "got:" << respCode;
00931 
00932   return respCode;
00933 }
00934 
00935 /* not really necessary, because the slave has to
00936    use the KIO::Error's instead, but let this here for
00937    documentation of the NNTP response codes and may
00938    by later use.
00939 QString& NNTPProtocol::errorStr(int resp_code) {
00940   QString ret;
00941 
00942   switch (resp_code) {
00943   case 100: ret = "help text follows"; break;
00944   case 199: ret = "debug output"; break;
00945 
00946   case 200: ret = "server ready - posting allowed"; break;
00947   case 201: ret = "server ready - no posting allowed"; break;
00948   case 202: ret = "slave status noted"; break;
00949   case 205: ret = "closing connection - goodbye!"; break;
00950   case 211: ret = "group selected"; break;
00951   case 215: ret = "list of newsgroups follows"; break;
00952   case 220: ret = "article retrieved - head and body follow"; break;
00953   case 221: ret = "article retrieved - head follows"; break;
00954   case 222: ret = "article retrieved - body follows"; break;
00955   case 223: ret = "article retrieved - request text separately"; break;
00956   case 230: ret = "list of new articles by message-id follows"; break;
00957   case 231: ret = "list of new newsgroups follows"; break;
00958   case 235: ret = "article transferred ok"; break;
00959   case 240: ret = "article posted ok"; break;
00960 
00961   case 335: ret = "send article to be transferred"; break;
00962   case 340: ret = "send article to be posted"; break;
00963 
00964   case 400: ret = "service discontinued"; break;
00965   case 411: ret = "no such news group"; break;
00966   case 412: ret = "no newsgroup has been selected"; break;
00967   case 420: ret = "no current article has been selected"; break;
00968   case 421: ret = "no next article in this group"; break;
00969   case 422: ret = "no previous article in this group"; break;
00970   case 423: ret = "no such article number in this group"; break;
00971   case 430: ret = "no such article found"; break;
00972   case 435: ret = "article not wanted - do not send it"; break;
00973   case 436: ret = "transfer failed - try again later"; break;
00974   case 437: ret = "article rejected - do not try again"; break;
00975   case 440: ret = "posting not allowed"; break;
00976   case 441: ret = "posting failed"; break;
00977 
00978   case 500: ret = "command not recognized"; break;
00979   case 501: ret = "command syntax error"; break;
00980   case 502: ret = "access restriction or permission denied"; break;
00981   case 503: ret = "program fault - command not performed"; break;
00982   default:  ret = QString("unknown NNTP response code %1").arg(resp_code);
00983   }
00984 
00985   return ret;
00986 }
00987 */

kioslave/nntp

Skip menu "kioslave/nntp"
  • Main Page
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.3
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