|
|
|
/* This file is part of KDE
|
|
|
|
Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de>
|
|
|
|
Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com>
|
|
|
|
Copyright (C) 2005 by Volker Krause <volker.krause@rwth-aachen.de>
|
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License version 2 as published by the Free Software Foundation.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#include <tqdir.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
|
|
|
|
#include <kinstance.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
|
|
|
|
#include "nntp.h"
|
|
|
|
|
|
|
|
#define NNTP_PORT 119
|
|
|
|
#define NNTPS_PORT 563
|
|
|
|
|
|
|
|
#define UDS_ENTRY_CHUNK 50 // so much entries are sent at once in listDir
|
|
|
|
|
|
|
|
#define DBG_AREA 7114
|
|
|
|
#define DBG kdDebug(DBG_AREA)
|
|
|
|
#define ERR kdError(DBG_AREA)
|
|
|
|
#define WRN kdWarning(DBG_AREA)
|
|
|
|
#define FAT kdFatal(DBG_AREA)
|
|
|
|
|
|
|
|
using namespace KIO;
|
|
|
|
|
|
|
|
extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
|
|
|
|
|
|
|
|
int kdemain(int argc, char **argv) {
|
|
|
|
|
|
|
|
KInstance instance ("kio_nntp");
|
|
|
|
if (argc != 4) {
|
|
|
|
fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
NNTPProtocol *slave;
|
|
|
|
|
|
|
|
// Are we going to use SSL?
|
|
|
|
if (strcasecmp(argv[1], "nntps") == 0) {
|
|
|
|
slave = new NNTPProtocol(argv[2], argv[3], true);
|
|
|
|
} else {
|
|
|
|
slave = new NNTPProtocol(argv[2], argv[3], false);
|
|
|
|
}
|
|
|
|
|
|
|
|
slave->dispatchLoop();
|
|
|
|
delete slave;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/****************** NNTPProtocol ************************/
|
|
|
|
|
|
|
|
NNTPProtocol::NNTPProtocol ( const TQCString & pool, const TQCString & app, bool isSSL )
|
|
|
|
: TCPSlaveBase( (isSSL ? NNTPS_PORT : NNTP_PORT), (isSSL ? "nntps" : "nntp"), pool,
|
|
|
|
app, isSSL )
|
|
|
|
{
|
|
|
|
DBG << "=============> NNTPProtocol::NNTPProtocol" << endl;
|
|
|
|
|
|
|
|
m_bIsSSL = isSSL;
|
|
|
|
readBufferLen = 0;
|
|
|
|
m_iDefaultPort = m_bIsSSL ? NNTPS_PORT : NNTP_PORT;
|
|
|
|
m_iPort = m_iDefaultPort;
|
|
|
|
}
|
|
|
|
|
|
|
|
NNTPProtocol::~NNTPProtocol() {
|
|
|
|
DBG << "<============= NNTPProtocol::~NNTPProtocol" << endl;
|
|
|
|
|
|
|
|
// close connection
|
|
|
|
nntp_close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::setHost ( const TQString & host, int port, const TQString & user,
|
|
|
|
const TQString & pass )
|
|
|
|
{
|
|
|
|
DBG << "setHost: " << ( ! user.isEmpty() ? (user+"@") : TQString(""))
|
|
|
|
<< host << ":" << ( ( port == 0 ) ? m_iDefaultPort : port ) << endl;
|
|
|
|
|
|
|
|
if ( isConnectionValid() && (mHost != host || m_iPort != port ||
|
|
|
|
mUser != user || mPass != pass) )
|
|
|
|
nntp_close();
|
|
|
|
|
|
|
|
mHost = host;
|
|
|
|
m_iPort = ( ( port == 0 ) ? m_iDefaultPort : port );
|
|
|
|
mUser = user;
|
|
|
|
mPass = pass;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::get(const KURL& url) {
|
|
|
|
DBG << "get " << url.prettyURL() << endl;
|
|
|
|
TQString path = TQDir::cleanDirPath(url.path());
|
|
|
|
TQRegExp regMsgId = TQRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false);
|
|
|
|
int pos;
|
|
|
|
TQString group;
|
|
|
|
TQString msg_id;
|
|
|
|
|
|
|
|
// path should be like: /group/<msg_id>
|
|
|
|
if (regMsgId.search(path) != 0) {
|
|
|
|
error(ERR_DOES_NOT_EXIST,path);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = path.find('<');
|
|
|
|
group = path.left(pos);
|
|
|
|
msg_id = KURL::decode_string( path.right(path.length()-pos) );
|
|
|
|
if (group.left(1) == "/") group.remove(0,1);
|
|
|
|
if ((pos = group.find('/')) > 0) group = group.left(pos);
|
|
|
|
DBG << "get group: " << group << " msg: " << msg_id << endl;
|
|
|
|
|
|
|
|
if ( !nntp_open() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// select group
|
|
|
|
int res_code = sendCommand( "GROUP " + group );
|
|
|
|
if (res_code == 411){
|
|
|
|
error(ERR_DOES_NOT_EXIST, path);
|
|
|
|
return;
|
|
|
|
} else if (res_code != 211) {
|
|
|
|
unexpected_response(res_code,"GROUP");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get article
|
|
|
|
res_code = sendCommand( "ARTICLE " + msg_id );
|
|
|
|
if (res_code == 430) {
|
|
|
|
error(ERR_DOES_NOT_EXIST,path);
|
|
|
|
return;
|
|
|
|
} else if (res_code != 220) {
|
|
|
|
unexpected_response(res_code,"ARTICLE");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read and send data
|
|
|
|
TQCString line;
|
|
|
|
TQByteArray buffer;
|
|
|
|
char tmp[MAX_PACKET_LEN];
|
|
|
|
int len = 0;
|
|
|
|
while ( true ) {
|
|
|
|
if ( !waitForResponse( readTimeout() ) ) {
|
|
|
|
error( ERR_SERVER_TIMEOUT, mHost );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
memset( tmp, 0, MAX_PACKET_LEN );
|
|
|
|
len = readLine( tmp, MAX_PACKET_LEN );
|
|
|
|
line = tmp;
|
|
|
|
if ( len <= 0 )
|
|
|
|
break;
|
|
|
|
if ( line == ".\r\n" )
|
|
|
|
break;
|
|
|
|
if ( line.left(2) == ".." )
|
|
|
|
line.remove( 0, 1 );
|
|
|
|
// cannot use TQCString, it would send the 0-terminator too
|
|
|
|
buffer.setRawData( line.data(), line.length() );
|
|
|
|
data( buffer );
|
|
|
|
buffer.resetRawData( line.data(), line.length() );
|
|
|
|
}
|
|
|
|
// end of data
|
|
|
|
buffer.resize(0);
|
|
|
|
data(buffer);
|
|
|
|
|
|
|
|
// finish
|
|
|
|
finished();
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::put( const KURL &/*url*/, int /*permissions*/, bool /*overwrite*/, bool /*resume*/ )
|
|
|
|
{
|
|
|
|
if ( !nntp_open() )
|
|
|
|
return;
|
|
|
|
if ( post_article() )
|
|
|
|
finished();
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::special(const TQByteArray& data) {
|
|
|
|
// 1 = post article
|
|
|
|
int cmd;
|
|
|
|
TQDataStream stream(data, IO_ReadOnly);
|
|
|
|
|
|
|
|
if ( !nntp_open() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
stream >> cmd;
|
|
|
|
if (cmd == 1) {
|
|
|
|
if (post_article()) finished();
|
|
|
|
} else {
|
|
|
|
error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1").arg(cmd));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NNTPProtocol::post_article() {
|
|
|
|
DBG << "post article " << endl;
|
|
|
|
|
|
|
|
// send post command
|
|
|
|
int res_code = sendCommand( "POST" );
|
|
|
|
if (res_code == 440) { // posting not allowed
|
|
|
|
error(ERR_WRITE_ACCESS_DENIED, mHost);
|
|
|
|
return false;
|
|
|
|
} else if (res_code != 340) { // 340: ok, send article
|
|
|
|
unexpected_response(res_code,"POST");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send article now
|
|
|
|
int result;
|
|
|
|
bool last_chunk_had_line_ending = true;
|
|
|
|
do {
|
|
|
|
TQByteArray buffer;
|
|
|
|
TQCString data;
|
|
|
|
dataReq();
|
|
|
|
result = readData(buffer);
|
|
|
|
// treat the buffer data
|
|
|
|
if (result>0) {
|
|
|
|
data = TQCString(buffer.data(),buffer.size()+1);
|
|
|
|
// translate "\r\n." to "\r\n.."
|
|
|
|
int pos=0;
|
|
|
|
if (last_chunk_had_line_ending && data[0] == '.') {
|
|
|
|
data.insert(0,'.');
|
|
|
|
pos += 2;
|
|
|
|
}
|
|
|
|
last_chunk_had_line_ending = (data.right(2) == "\r\n");
|
|
|
|
while ((pos = data.find("\r\n.",pos)) > 0) {
|
|
|
|
data.insert(pos+2,'.');
|
|
|
|
pos += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send data to socket, write() doesn't send the terminating 0
|
|
|
|
write( data.data(), data.length() );
|
|
|
|
}
|
|
|
|
} while (result>0);
|
|
|
|
|
|
|
|
// error occurred?
|
|
|
|
if (result<0) {
|
|
|
|
ERR << "error while getting article data for posting" << endl;
|
|
|
|
nntp_close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send end mark
|
|
|
|
write( "\r\n.\r\n", 5 );
|
|
|
|
|
|
|
|
// get answer
|
|
|
|
res_code = evalResponse( readBuffer, readBufferLen );
|
|
|
|
if (res_code == 441) { // posting failed
|
|
|
|
error(ERR_COULD_NOT_WRITE, mHost);
|
|
|
|
return false;
|
|
|
|
} else if (res_code != 240) {
|
|
|
|
unexpected_response(res_code,"POST");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NNTPProtocol::stat( const KURL& url ) {
|
|
|
|
DBG << "stat " << url.prettyURL() << endl;
|
|
|
|
UDSEntry entry;
|
|
|
|
TQString path = TQDir::cleanDirPath(url.path());
|
|
|
|
TQRegExp regGroup = TQRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",false);
|
|
|
|
TQRegExp regMsgId = TQRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false);
|
|
|
|
int pos;
|
|
|
|
TQString group;
|
|
|
|
TQString msg_id;
|
|
|
|
|
|
|
|
// / = group list
|
|
|
|
if (path.isEmpty() || path == "/") {
|
|
|
|
DBG << "stat root" << endl;
|
|
|
|
fillUDSEntry(entry, TQString::null, 0, postingAllowed, false);
|
|
|
|
|
|
|
|
// /group = message list
|
|
|
|
} else if (regGroup.search(path) == 0) {
|
|
|
|
if (path.left(1) == "/") path.remove(0,1);
|
|
|
|
if ((pos = path.find('/')) > 0) group = path.left(pos);
|
|
|
|
else group = path;
|
|
|
|
DBG << "stat group: " << group << endl;
|
|
|
|
// postingAllowed should be ored here with "group not moderated" flag
|
|
|
|
// as size the num of messages (GROUP cmd) could be given
|
|
|
|
fillUDSEntry(entry, group, 0, postingAllowed, false);
|
|
|
|
|
|
|
|
// /group/<msg_id> = message
|
|
|
|
} else if (regMsgId.search(path) == 0) {
|
|
|
|
pos = path.find('<');
|
|
|
|
group = path.left(pos);
|
|
|
|
msg_id = KURL::decode_string( path.right(path.length()-pos) );
|
|
|
|
if (group.left(1) == "/") group.remove(0,1);
|
|
|
|
if ((pos = group.find('/')) > 0) group = group.left(pos);
|
|
|
|
DBG << "stat group: " << group << " msg: " << msg_id << endl;
|
|
|
|
fillUDSEntry(entry, msg_id, 0, false, true);
|
|
|
|
|
|
|
|
// invalid url
|
|
|
|
} else {
|
|
|
|
error(ERR_DOES_NOT_EXIST,path);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
statEntry(entry);
|
|
|
|
finished();
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::listDir( const KURL& url ) {
|
|
|
|
DBG << "listDir " << url.prettyURL() << endl;
|
|
|
|
if ( !nntp_open() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQString path = TQDir::cleanDirPath(url.path());
|
|
|
|
|
|
|
|
if (path.isEmpty())
|
|
|
|
{
|
|
|
|
KURL newURL(url);
|
|
|
|
newURL.setPath("/");
|
|
|
|
DBG << "listDir redirecting to " << newURL.prettyURL() << endl;
|
|
|
|
redirection(newURL);
|
|
|
|
finished();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if ( path == "/" ) {
|
|
|
|
fetchGroups( url.queryItem( "since" ) );
|
|
|
|
finished();
|
|
|
|
} else {
|
|
|
|
// if path = /group
|
|
|
|
int pos;
|
|
|
|
TQString group;
|
|
|
|
if (path.left(1) == "/")
|
|
|
|
path.remove(0,1);
|
|
|
|
if ((pos = path.find('/')) > 0)
|
|
|
|
group = path.left(pos);
|
|
|
|
else
|
|
|
|
group = path;
|
|
|
|
TQString first = url.queryItem( "first" );
|
|
|
|
if ( fetchGroup( group, first.toULong() ) )
|
|
|
|
finished();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::fetchGroups( const TQString &since )
|
|
|
|
{
|
|
|
|
int expected;
|
|
|
|
int res;
|
|
|
|
if ( since.isEmpty() ) {
|
|
|
|
// full listing
|
|
|
|
res = sendCommand( "LIST" );
|
|
|
|
expected = 215;
|
|
|
|
} else {
|
|
|
|
// incremental listing
|
|
|
|
res = sendCommand( "NEWGROUPS " + since );
|
|
|
|
expected = 231;
|
|
|
|
}
|
|
|
|
if ( res != expected ) {
|
|
|
|
unexpected_response( res, "LIST" );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read newsgroups line by line
|
|
|
|
TQCString line, group;
|
|
|
|
int pos, pos2;
|
|
|
|
long msg_cnt;
|
|
|
|
bool moderated;
|
|
|
|
UDSEntry entry;
|
|
|
|
UDSEntryList entryList;
|
|
|
|
|
|
|
|
// read in data and process each group. one line at a time
|
|
|
|
while ( true ) {
|
|
|
|
if ( ! waitForResponse( readTimeout() ) ) {
|
|
|
|
error( ERR_SERVER_TIMEOUT, mHost );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
memset( readBuffer, 0, MAX_PACKET_LEN );
|
|
|
|
readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
|
|
|
|
line = readBuffer;
|
|
|
|
if ( line == ".\r\n" )
|
|
|
|
break;
|
|
|
|
|
|
|
|
DBG << " fetchGroups -- data: " << line.stripWhiteSpace() << endl;
|
|
|
|
|
|
|
|
// group name
|
|
|
|
if ((pos = line.find(' ')) > 0) {
|
|
|
|
|
|
|
|
group = line.left(pos);
|
|
|
|
|
|
|
|
// number of messages
|
|
|
|
line.remove(0,pos+1);
|
|
|
|
long last = 0;
|
|
|
|
if (((pos = line.find(' ')) > 0 || (pos = line.find('\t')) > 0) &&
|
|
|
|
((pos2 = line.find(' ',pos+1)) > 0 || (pos2 = line.find('\t',pos+1)) > 0)) {
|
|
|
|
last = line.left(pos).toLong();
|
|
|
|
long first = line.mid(pos+1,pos2-pos-1).toLong();
|
|
|
|
msg_cnt = abs(last-first+1);
|
|
|
|
// moderated group?
|
|
|
|
moderated = (line[pos2+1] == 'n');
|
|
|
|
} else {
|
|
|
|
msg_cnt = 0;
|
|
|
|
moderated = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
fillUDSEntry(entry, group, msg_cnt, postingAllowed && !moderated, false);
|
|
|
|
// add the last serial number as UDS_EXTRA atom, this is needed for
|
|
|
|
// incremental article listing
|
|
|
|
UDSAtom atom;
|
|
|
|
atom.m_uds = UDS_EXTRA;
|
|
|
|
atom.m_str = TQString::number( last );
|
|
|
|
entry.append( atom );
|
|
|
|
entryList.append(entry);
|
|
|
|
|
|
|
|
if (entryList.count() >= UDS_ENTRY_CHUNK) {
|
|
|
|
listEntries(entryList);
|
|
|
|
entryList.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// send rest of entryList
|
|
|
|
if (entryList.count() > 0) listEntries(entryList);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NNTPProtocol::fetchGroup( TQString &group, unsigned long first ) {
|
|
|
|
int res_code;
|
|
|
|
TQString resp_line;
|
|
|
|
|
|
|
|
// select group
|
|
|
|
res_code = sendCommand( "GROUP " + group );
|
|
|
|
if (res_code == 411){
|
|
|
|
error(ERR_DOES_NOT_EXIST,group);
|
|
|
|
return false;
|
|
|
|
} else if (res_code != 211) {
|
|
|
|
unexpected_response(res_code,"GROUP");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// repsonse to "GROUP <requested-group>" command is 211 then find the message count (cnt)
|
|
|
|
// and the first and last message followed by the group name
|
|
|
|
int pos, pos2;
|
|
|
|
unsigned long firstSerNum;
|
|
|
|
resp_line = readBuffer;
|
|
|
|
if (((pos = resp_line.find(' ',4)) > 0 || (pos = resp_line.find('\t',4)) > 0) &&
|
|
|
|
((pos2 = resp_line.find(' ',pos+1)) > 0 || (pos = resp_line.find('\t',pos+1)) > 0))
|
|
|
|
{
|
|
|
|
firstSerNum = resp_line.mid(pos+1,pos2-pos-1).toLong();
|
|
|
|
} else {
|
|
|
|
error(ERR_INTERNAL,i18n("Could not extract first message number from server response:\n%1").
|
|
|
|
arg(resp_line));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstSerNum == 0L)
|
|
|
|
return true;
|
|
|
|
first = kMax( first, firstSerNum );
|
|
|
|
DBG << "Starting from serial number: " << first << " of " << firstSerNum << endl;
|
|
|
|
|
|
|
|
bool notSupported = true;
|
|
|
|
if ( fetchGroupXOVER( first, notSupported ) )
|
|
|
|
return true;
|
|
|
|
else if ( notSupported )
|
|
|
|
return fetchGroupRFC977( first );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool NNTPProtocol::fetchGroupRFC977( unsigned long first )
|
|
|
|
{
|
|
|
|
UDSEntry entry;
|
|
|
|
UDSEntryList entryList;
|
|
|
|
|
|
|
|
// set article pointer to first article and get msg-id of it
|
|
|
|
int res_code = sendCommand( "STAT " + TQString::number( first ) );
|
|
|
|
TQString resp_line = readBuffer;
|
|
|
|
if (res_code != 223) {
|
|
|
|
unexpected_response(res_code,"STAT");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//STAT res_line: 223 nnn <msg_id> ...
|
|
|
|
TQString msg_id;
|
|
|
|
int pos, pos2;
|
|
|
|
if ((pos = resp_line.find('<')) > 0 && (pos2 = resp_line.find('>',pos+1))) {
|
|
|
|
msg_id = resp_line.mid(pos,pos2-pos+1);
|
|
|
|
fillUDSEntry(entry, msg_id, 0, false, true);
|
|
|
|
entryList.append(entry);
|
|
|
|
} else {
|
|
|
|
error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1").
|
|
|
|
arg(resp_line));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// go through all articles
|
|
|
|
while (true) {
|
|
|
|
res_code = sendCommand("NEXT");
|
|
|
|
if (res_code == 421) {
|
|
|
|
// last article reached
|
|
|
|
if ( !entryList.isEmpty() )
|
|
|
|
listEntries( entryList );
|
|
|
|
return true;
|
|
|
|
} else if (res_code != 223) {
|
|
|
|
unexpected_response(res_code,"NEXT");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//res_line: 223 nnn <msg_id> ...
|
|
|
|
resp_line = readBuffer;
|
|
|
|
if ((pos = resp_line.find('<')) > 0 && (pos2 = resp_line.find('>',pos+1))) {
|
|
|
|
msg_id = resp_line.mid(pos,pos2-pos+1);
|
|
|
|
fillUDSEntry(entry, msg_id, 0, false, true);
|
|
|
|
entryList.append(entry);
|
|
|
|
if (entryList.count() >= UDS_ENTRY_CHUNK) {
|
|
|
|
listEntries(entryList);
|
|
|
|
entryList.clear();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1").
|
|
|
|
arg(resp_line));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true; // Not reached
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool ¬Supported )
|
|
|
|
{
|
|
|
|
notSupported = false;
|
|
|
|
|
|
|
|
TQString line;
|
|
|
|
TQStringList headers;
|
|
|
|
|
|
|
|
int res = sendCommand( "LIST OVERVIEW.FMT" );
|
|
|
|
if ( res == 215 ) {
|
|
|
|
while ( true ) {
|
|
|
|
if ( ! waitForResponse( readTimeout() ) ) {
|
|
|
|
error( ERR_SERVER_TIMEOUT, mHost );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memset( readBuffer, 0, MAX_PACKET_LEN );
|
|
|
|
readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
|
|
|
|
line = readBuffer;
|
|
|
|
if ( line == ".\r\n" )
|
|
|
|
break;
|
|
|
|
headers << line.stripWhiteSpace();
|
|
|
|
DBG << "OVERVIEW.FMT: " << line.stripWhiteSpace() << endl;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// fallback to defaults
|
|
|
|
headers << "Subject:" << "From:" << "Date:" << "Message-ID:"
|
|
|
|
<< "References:" << "Bytes:" << "Lines:";
|
|
|
|
}
|
|
|
|
|
|
|
|
res = sendCommand( "XOVER " + TQString::number( first ) + "-" );
|
|
|
|
if ( res == 420 )
|
|
|
|
return true; // no articles selected
|
|
|
|
if ( res == 500 )
|
|
|
|
notSupported = true; // unknwon command
|
|
|
|
if ( res != 224 )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
long msgSize;
|
|
|
|
TQString msgId;
|
|
|
|
UDSAtom atom;
|
|
|
|
UDSEntry entry;
|
|
|
|
UDSEntryList entryList;
|
|
|
|
|
|
|
|
TQStringList fields;
|
|
|
|
while ( true ) {
|
|
|
|
if ( ! waitForResponse( readTimeout() ) ) {
|
|
|
|
error( ERR_SERVER_TIMEOUT, mHost );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memset( readBuffer, 0, MAX_PACKET_LEN );
|
|
|
|
readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
|
|
|
|
line = readBuffer;
|
|
|
|
if ( line == ".\r\n" ) {
|
|
|
|
// last article reached
|
|
|
|
if ( !entryList.isEmpty() )
|
|
|
|
listEntries( entryList );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
fields = TQStringList::split( "\t", line, true );
|
|
|
|
msgId = TQString::null;
|
|
|
|
msgSize = 0;
|
|
|
|
TQStringList::ConstIterator it = headers.constBegin();
|
|
|
|
TQStringList::ConstIterator it2 = fields.constBegin();
|
|
|
|
++it2; // first entry is the serial number
|
|
|
|
for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) {
|
|
|
|
if ( (*it).contains( "Message-ID:", false ) ) {
|
|
|
|
msgId = (*it2);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( (*it) == "Bytes:" ) {
|
|
|
|
msgSize = (*it2).toLong();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
atom.m_uds = UDS_EXTRA;
|
|
|
|
if ( (*it).endsWith( "full" ) )
|
|
|
|
atom.m_str = (*it2).stripWhiteSpace();
|
|
|
|
else
|
|
|
|
atom.m_str = (*it) + " " + (*it2).stripWhiteSpace();
|
|
|
|
entry.append( atom );
|
|
|
|
}
|
|
|
|
if ( msgId.isEmpty() )
|
|
|
|
msgId = fields[0]; // fallback to serial number
|
|
|
|
fillUDSEntry( entry, msgId, msgSize, false, true );
|
|
|
|
entryList.append( entry );
|
|
|
|
if (entryList.count() >= UDS_ENTRY_CHUNK) {
|
|
|
|
listEntries(entryList);
|
|
|
|
entryList.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NNTPProtocol::fillUDSEntry(UDSEntry& entry, const TQString& name, long size,
|
|
|
|
bool posting_allowed, bool is_article) {
|
|
|
|
|
|
|
|
long posting=0;
|
|
|
|
|
|
|
|
UDSAtom atom;
|
|
|
|
entry.clear();
|
|
|
|
|
|
|
|
// entry name
|
|
|
|
atom.m_uds = UDS_NAME;
|
|
|
|
atom.m_str = name;
|
|
|
|
atom.m_long = 0;
|
|
|
|
entry.append(atom);
|
|
|
|
|
|
|
|
// entry size
|
|
|
|
atom.m_uds = UDS_SIZE;
|
|
|
|
atom.m_str = TQString::null;
|
|
|
|
atom.m_long = size;
|
|
|
|
entry.append(atom);
|
|
|
|
|
|
|
|
// file type
|
|
|
|
atom.m_uds = UDS_FILE_TYPE;
|
|
|
|
atom.m_long = is_article? S_IFREG : S_IFDIR;
|
|
|
|
atom.m_str = TQString::null;
|
|
|
|
entry.append(atom);
|
|
|
|
|
|
|
|
// access permissions
|
|
|
|
atom.m_uds = UDS_ACCESS;
|
|
|
|
posting = posting_allowed? (S_IWUSR | S_IWGRP | S_IWOTH) : 0;
|
|
|
|
atom.m_long = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) :
|
|
|
|
(S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting);
|
|
|
|
atom.m_str = TQString::null;
|
|
|
|
entry.append(atom);
|
|
|
|
|
|
|
|
atom.m_uds = UDS_USER;
|
|
|
|
atom.m_str = mUser.isEmpty() ? TQString("root") : mUser;
|
|
|
|
atom.m_long= 0;
|
|
|
|
entry.append(atom);
|
|
|
|
|
|
|
|
/*
|
|
|
|
atom.m_uds = UDS_GROUP;
|
|
|
|
atom.m_str = "root";
|
|
|
|
atom.m_long=0;
|
|
|
|
entry->append(atom);
|
|
|
|
*/
|
|
|
|
|
|
|
|
// MIME type
|
|
|
|
if (is_article) {
|
|
|
|
atom.m_uds = UDS_MIME_TYPE;
|
|
|
|
atom.m_long= 0;
|
|
|
|
atom.m_str = "message/news";
|
|
|
|
entry.append(atom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::nntp_close () {
|
|
|
|
if ( isConnectionValid() ) {
|
|
|
|
write( "QUIT\r\n", 6 );
|
|
|
|
closeDescriptor();
|
|
|
|
opened = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NNTPProtocol::nntp_open()
|
|
|
|
{
|
|
|
|
// if still connected reuse connection
|
|
|
|
if ( isConnectionValid() ) {
|
|
|
|
DBG << "reusing old connection" << endl;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG << " nntp_open -- creating a new connection to " << mHost << ":" << m_iPort << endl;
|
|
|
|
// create a new connection
|
|
|
|
if ( connectToHost( mHost.latin1(), m_iPort, true ) )
|
|
|
|
{
|
|
|
|
DBG << " nntp_open -- connection is open " << endl;
|
|
|
|
|
|
|
|
// read greeting
|
|
|
|
int res_code = evalResponse( readBuffer, readBufferLen );
|
|
|
|
|
|
|
|
/* expect one of
|
|
|
|
200 server ready - posting allowed
|
|
|
|
201 server ready - no posting allowed
|
|
|
|
*/
|
|
|
|
if ( ! ( res_code == 200 || res_code == 201 ) )
|
|
|
|
{
|
|
|
|
unexpected_response(res_code,"CONNECT");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG << " nntp_open -- greating was read res_code : " << res_code << endl;
|
|
|
|
// let local class know that we are connected
|
|
|
|
opened = true;
|
|
|
|
|
|
|
|
res_code = sendCommand("MODE READER");
|
|
|
|
|
|
|
|
// TODO: not in RFC 977, so we should not abort here
|
|
|
|
if ( !(res_code == 200 || res_code == 201) ) {
|
|
|
|
unexpected_response( res_code, "MODE READER" );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// let local class know whether posting is allowed or not
|
|
|
|
postingAllowed = (res_code == 200);
|
|
|
|
|
|
|
|
// activate TLS if requested
|
|
|
|
if ( metaData("tls") == "on" ) {
|
|
|
|
if ( sendCommand( "STARTTLS" ) != 382 ) {
|
|
|
|
error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
int tlsrc = startTLS();
|
|
|
|
if ( tlsrc != 1 ) {
|
|
|
|
error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// connection attempt failed
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DBG << " nntp_open -- connection attempt failed" << endl;
|
|
|
|
error( ERR_COULD_NOT_CONNECT, mHost );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int NNTPProtocol::sendCommand( const TQString &cmd )
|
|
|
|
{
|
|
|
|
int res_code = 0;
|
|
|
|
|
|
|
|
if ( !opened ) {
|
|
|
|
ERR << "NOT CONNECTED, cannot send cmd " << cmd << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBG << "sending cmd " << cmd << endl;
|
|
|
|
|
|
|
|
write( cmd.latin1(), cmd.length() );
|
|
|
|
// check the command for proper termination
|
|
|
|
if ( !cmd.endsWith( "\r\n" ) )
|
|
|
|
write( "\r\n", 2 );
|
|
|
|
res_code = evalResponse( readBuffer, readBufferLen );
|
|
|
|
|
|
|
|
// if authorization needed send user info
|
|
|
|
if (res_code == 480) {
|
|
|
|
DBG << "auth needed, sending user info" << endl;
|
|
|
|
|
|
|
|
if ( mUser.isEmpty() || mPass.isEmpty() ) {
|
|
|
|
KIO::AuthInfo authInfo;
|
|
|
|
authInfo.username = mUser;
|
|
|
|
authInfo.password = mPass;
|
|
|
|
if ( openPassDlg( authInfo ) ) {
|
|
|
|
mUser = authInfo.username;
|
|
|
|
mPass = authInfo.password;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( mUser.isEmpty() || mPass.isEmpty() )
|
|
|
|
return res_code;
|
|
|
|
|
|
|
|
// send username to server and confirm response
|
|
|
|
write( "AUTHINFO USER ", 14 );
|
|
|
|
write( mUser.latin1(), mUser.length() );
|
|
|
|
write( "\r\n", 2 );
|
|
|
|
res_code = evalResponse( readBuffer, readBufferLen );
|
|
|
|
|
|
|
|
if (res_code != 381) {
|
|
|
|
// error should be handled by invoking function
|
|
|
|
return res_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send password
|
|
|
|
write( "AUTHINFO PASS ", 14 );
|
|
|
|
write( mPass.latin1(), mPass.length() );
|
|
|
|
write( "\r\n", 2 );
|
|
|
|
res_code = evalResponse( readBuffer, readBufferLen );
|
|
|
|
|
|
|
|
if (res_code != 281) {
|
|
|
|
// error should be handled by invoking function
|
|
|
|
return res_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ok now, resend command
|
|
|
|
write( cmd.latin1(), cmd.length() );
|
|
|
|
if ( !cmd.endsWith( "\r\n" ) )
|
|
|
|
write( "\r\n", 2 );
|
|
|
|
res_code = evalResponse( readBuffer, readBufferLen );
|
|
|
|
}
|
|
|
|
|
|
|
|
return res_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NNTPProtocol::unexpected_response( int res_code, const TQString & command) {
|
|
|
|
ERR << "Unexpected response to " << command << " command: (" << res_code << ") "
|
|
|
|
<< readBuffer << endl;
|
|
|
|
error(ERR_INTERNAL,i18n("Unexpected server response to %1 command:\n%2").
|
|
|
|
arg(command).arg(readBuffer));
|
|
|
|
|
|
|
|
// close connection
|
|
|
|
nntp_close();
|
|
|
|
}
|
|
|
|
|
|
|
|
int NNTPProtocol::evalResponse ( char *data, ssize_t &len )
|
|
|
|
{
|
|
|
|
if ( !waitForResponse( responseTimeout() ) ) {
|
|
|
|
error( ERR_SERVER_TIMEOUT , mHost );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
memset( data, 0, MAX_PACKET_LEN );
|
|
|
|
len = readLine( data, MAX_PACKET_LEN );
|
|
|
|
|
|
|
|
if ( len < 3 )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
// get the first three characters. should be the response code
|
|
|
|
int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) );
|
|
|
|
|
|
|
|
DBG << "evalResponse - got: " << respCode << endl;
|
|
|
|
|
|
|
|
return respCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* not really necessary, because the slave has to
|
|
|
|
use the KIO::Error's instead, but let this here for
|
|
|
|
documentation of the NNTP response codes and may
|
|
|
|
by later use.
|
|
|
|
TQString& NNTPProtocol::errorStr(int resp_code) {
|
|
|
|
TQString ret;
|
|
|
|
|
|
|
|
switch (resp_code) {
|
|
|
|
case 100: ret = "help text follows"; break;
|
|
|
|
case 199: ret = "debug output"; break;
|
|
|
|
|
|
|
|
case 200: ret = "server ready - posting allowed"; break;
|
|
|
|
case 201: ret = "server ready - no posting allowed"; break;
|
|
|
|
case 202: ret = "slave status noted"; break;
|
|
|
|
case 205: ret = "closing connection - goodbye!"; break;
|
|
|
|
case 211: ret = "group selected"; break;
|
|
|
|
case 215: ret = "list of newsgroups follows"; break;
|
|
|
|
case 220: ret = "article retrieved - head and body follow"; break;
|
|
|
|
case 221: ret = "article retrieved - head follows"; break;
|
|
|
|
case 222: ret = "article retrieved - body follows"; break;
|
|
|
|
case 223: ret = "article retrieved - request text separately"; break;
|
|
|
|
case 230: ret = "list of new articles by message-id follows"; break;
|
|
|
|
case 231: ret = "list of new newsgroups follows"; break;
|
|
|
|
case 235: ret = "article transferred ok"; break;
|
|
|
|
case 240: ret = "article posted ok"; break;
|
|
|
|
|
|
|
|
case 335: ret = "send article to be transferred"; break;
|
|
|
|
case 340: ret = "send article to be posted"; break;
|
|
|
|
|
|
|
|
case 400: ret = "service discontinued"; break;
|
|
|
|
case 411: ret = "no such news group"; break;
|
|
|
|
case 412: ret = "no newsgroup has been selected"; break;
|
|
|
|
case 420: ret = "no current article has been selected"; break;
|
|
|
|
case 421: ret = "no next article in this group"; break;
|
|
|
|
case 422: ret = "no previous article in this group"; break;
|
|
|
|
case 423: ret = "no such article number in this group"; break;
|
|
|
|
case 430: ret = "no such article found"; break;
|
|
|
|
case 435: ret = "article not wanted - do not send it"; break;
|
|
|
|
case 436: ret = "transfer failed - try again later"; break;
|
|
|
|
case 437: ret = "article rejected - do not try again"; break;
|
|
|
|
case 440: ret = "posting not allowed"; break;
|
|
|
|
case 441: ret = "posting failed"; break;
|
|
|
|
|
|
|
|
case 500: ret = "command not recognized"; break;
|
|
|
|
case 501: ret = "command syntax error"; break;
|
|
|
|
case 502: ret = "access restriction or permission denied"; break;
|
|
|
|
case 503: ret = "program fault - command not performed"; break;
|
|
|
|
default: ret = TQString("unknown NNTP response code %1").arg(resp_code);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
*/
|