You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/tdeioslave/nntp/nntp.cpp

897 lines
24 KiB

/* 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 <tdeglobal.h>
#include <tdelocale.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 TDEIO;
extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
int kdemain(int argc, char **argv) {
TDEInstance instance ("tdeio_nntp");
if (argc != 4) {
fprintf(stderr, "Usage: tdeio_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 &notSupported )
{
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() ) {
TDEIO::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 TDEIO::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;
}
*/