diff options
Diffstat (limited to 'kioslave/nntp')
-rw-r--r-- | kioslave/nntp/LICENSE | 16 | ||||
-rw-r--r-- | kioslave/nntp/Makefile.am | 19 | ||||
-rw-r--r-- | kioslave/nntp/nntp.cpp | 896 | ||||
-rw-r--r-- | kioslave/nntp/nntp.h | 130 | ||||
-rw-r--r-- | kioslave/nntp/nntp.protocol | 11 | ||||
-rw-r--r-- | kioslave/nntp/nntps.protocol | 11 |
6 files changed, 1083 insertions, 0 deletions
diff --git a/kioslave/nntp/LICENSE b/kioslave/nntp/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/kioslave/nntp/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/nntp/Makefile.am b/kioslave/nntp/Makefile.am new file mode 100644 index 000000000..b06d31600 --- /dev/null +++ b/kioslave/nntp/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(all_includes) + +####### Files + +kde_module_LTLIBRARIES = kio_nntp.la + +kio_nntp_la_SOURCES = nntp.cpp +kio_nntp_la_LIBADD = $(LIB_KIO) +kio_nntp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +METASOURCES = AUTO + +noinst_HEADERS = nntp.h + +kdelnk_DATA = nntp.protocol nntps.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_nntp.pot diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp new file mode 100644 index 000000000..40b162868 --- /dev/null +++ b/kioslave/nntp/nntp.cpp @@ -0,0 +1,896 @@ +/* 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 <qdir.h> +#include <qregexp.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 QCString & pool, const QCString & 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 QString & host, int port, const QString & user, + const QString & pass ) +{ + DBG << "setHost: " << ( ! user.isEmpty() ? (user+"@") : QString("")) + << 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; + QString path = QDir::cleanDirPath(url.path()); + QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false); + int pos; + QString group; + QString 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 + QCString line; + QByteArray 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 QCString, 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 QByteArray& data) { + // 1 = post article + int cmd; + QDataStream 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 { + QByteArray buffer; + QCString data; + dataReq(); + result = readData(buffer); + // treat the buffer data + if (result>0) { + data = QCString(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; + QString path = QDir::cleanDirPath(url.path()); + QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",false); + QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false); + int pos; + QString group; + QString msg_id; + + // / = group list + if (path.isEmpty() || path == "/") { + DBG << "stat root" << endl; + fillUDSEntry(entry, QString::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; + + QString path = QDir::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; + QString group; + if (path.left(1) == "/") + path.remove(0,1); + if ((pos = path.find('/')) > 0) + group = path.left(pos); + else + group = path; + QString first = url.queryItem( "first" ); + if ( fetchGroup( group, first.toULong() ) ) + finished(); + } +} + +void NNTPProtocol::fetchGroups( const QString &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 + QCString 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 = QString::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( QString &group, unsigned long first ) { + int res_code; + QString 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 " + QString::number( first ) ); + QString resp_line = readBuffer; + if (res_code != 223) { + unexpected_response(res_code,"STAT"); + return false; + } + + //STAT res_line: 223 nnn <msg_id> ... + QString 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; + + QString line; + QStringList 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 " + QString::number( first ) + "-" ); + if ( res == 420 ) + return true; // no articles selected + if ( res == 500 ) + notSupported = true; // unknwon command + if ( res != 224 ) + return false; + + long msgSize; + QString msgId; + UDSAtom atom; + UDSEntry entry; + UDSEntryList entryList; + + QStringList 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 = QStringList::split( "\t", line, true ); + msgId = QString::null; + msgSize = 0; + QStringList::ConstIterator it = headers.constBegin(); + QStringList::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 QString& 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 = QString::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 = QString::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 = QString::null; + entry.append(atom); + + atom.m_uds = UDS_USER; + atom.m_str = mUser.isEmpty() ? QString("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 QString &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 QString & 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. +QString& NNTPProtocol::errorStr(int resp_code) { + QString 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 = QString("unknown NNTP response code %1").arg(resp_code); + } + + return ret; +} +*/ diff --git a/kioslave/nntp/nntp.h b/kioslave/nntp/nntp.h new file mode 100644 index 000000000..7efe597a8 --- /dev/null +++ b/kioslave/nntp/nntp.h @@ -0,0 +1,130 @@ +/* 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. +*/ + +#ifndef _NNTP_H +#define _NNTP_H + +#include <qstring.h> +#include <kio/global.h> +#include <kio/tcpslavebase.h> + +#define MAX_PACKET_LEN 4096 + +/* TODO: + - test special post command + - progress information in get, and maybe post + - remove unnecessary debug stuff +*/ + +class NNTPProtocol:public KIO::TCPSlaveBase +{ + + public: + /** Default Constructor + * @param isSSL is a true or false to indicate whether ssl is to be used + */ + NNTPProtocol ( const QCString & pool, const QCString & app, bool isSSL ); + virtual ~NNTPProtocol(); + + virtual void get(const KURL& url ); + virtual void put( const KURL& url, int permissions, bool overwrite, bool resume ); + virtual void stat(const KURL& url ); + virtual void listDir(const KURL& url ); + virtual void setHost(const QString& host, int port, + const QString& user, const QString& pass); + + /** + * Special command: 1 = post article + * it takes no other args, the article data are + * requested by dataReq() and should be valid + * as in RFC850. It's not checked for correctness here. + * @deprecated use put() for posting + */ + virtual void special(const QByteArray& data); + + protected: + + /** + * Send a command to the server. Returns the response code and + * the response line + */ + int sendCommand( const QString &cmd ); + + /** + * Attempt to properly shut down the NNTP connection by sending + * "QUIT\r\n" before closing the socket. + */ + void nntp_close (); + + /** + * Attempt to initiate a NNTP connection via a TCP socket, if no existing + * connection could be reused. + */ + bool nntp_open(); + + /** + * Post article. Invoked by special() and put() + */ + bool post_article(); + + + private: + QString mHost, mUser, mPass; + bool postingAllowed, opened; + char readBuffer[MAX_PACKET_LEN]; + ssize_t readBufferLen; + + /** + * Fetch all new groups since the given date or (if the date is empty) + * all available groups. + * @param since Date as specified in RFC 977 for the NEWGROUPS command + */ + void fetchGroups( const QString &since ); + /** + * Fetch message listing from the given newsgroup. + * This will use RFC2980 XOVER if available, plain RFC977 STAT/NEXT + * otherwise. + * @param group The newsgroup name + * @param first Serial number of the first message, 0 lists all messages. + * @return true on sucess, false otherwise. + */ + bool fetchGroup ( QString &group, unsigned long first = 0 ); + /** + * Fetch message listing from the current group using RFC977 STAT/NEXT + * commands. + * @param first message number of the first article + * @return true on sucess, false otherwise. + */ + bool fetchGroupRFC977( unsigned long first ); + /** + * Fetch message listing from the current group using the RFC2980 XOVER + * command. + * Additional headers provided by XOVER are added as UDS_EXTRA entries + * to the listing. + * @param first message number of the first article + * @param notSupported boolean reference to indicate if command failed + * due to missing XOVER support on the server. + * @return true on sucess, false otherwise + */ + bool fetchGroupXOVER( unsigned long first, bool ¬Supported ); + /// creates an UDSEntry with file information used in stat and listDir + void fillUDSEntry ( KIO::UDSEntry & entry, const QString & name, long size, + bool postingAllowed, bool is_article ); + /// error handling for unexpected responses + void unexpected_response ( int res_code, const QString & command ); + /** + * grabs the response line from the server. used after most send_cmd calls. max + * length for the returned string ( char *data ) is 4096 characters including + * the "\r\n" terminator. + */ + int evalResponse ( char *data, ssize_t &len ); +}; + +#endif diff --git a/kioslave/nntp/nntp.protocol b/kioslave/nntp/nntp.protocol new file mode 100644 index 000000000..1ae8a25f7 --- /dev/null +++ b/kioslave/nntp/nntp.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_nntp +protocol=nntp +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=true +deleting=false +DocPath=kioslave/nntp.html +Icon=news diff --git a/kioslave/nntp/nntps.protocol b/kioslave/nntp/nntps.protocol new file mode 100644 index 000000000..4d2e61422 --- /dev/null +++ b/kioslave/nntp/nntps.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_nntp +protocol=nntps +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=true +deleting=false +DocPath=kioslave/nntp.html +Icon=news |