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 ¬Supported ) 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 */
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 04:40:58 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon May 14 2012 04:40:58 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.