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

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