• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 API Reference
  • KDE Home
  • Contact Us
 

kioslave/nntp

  • kioslave
  • nntp
nntp.cpp
1/* This file is part of KDE
2 Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de>
3 Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com>
4 Copyright (C) 2005 by Volker Krause <vkrause@kde.org>
5
6 This is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License version 2 as published by the Free Software Foundation.
9*/
10
11#include "nntp.h"
12
13#include <sys/stat.h>
14#include <stdlib.h>
15#include <stdio.h>
16
17#include <QDir>
18#include <QHash>
19#include <QRegExp>
20
21#include <kcomponentdata.h>
22#include <kdebug.h>
23#include <klocalizedstring.h>
24
25#include <kio/ioslave_defaults.h>
26
27#define DBG_AREA 7114
28#define DBG kDebug(DBG_AREA)
29
30#undef ERR
31#define ERR kError(DBG_AREA)
32
33using namespace KIO;
34
35extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
36
37int kdemain(int argc, char **argv) {
38
39 KComponentData componentData("kio_nntp");
40 if (argc != 4) {
41 fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n");
42 exit(-1);
43 }
44
45 NNTPProtocol *slave;
46
47 // Are we going to use SSL?
48 if (strcasecmp(argv[1], "nntps") == 0) {
49 slave = new NNTPProtocol(argv[2], argv[3], true);
50 } else {
51 slave = new NNTPProtocol(argv[2], argv[3], false);
52 }
53
54 slave->dispatchLoop();
55 delete slave;
56
57 return 0;
58}
59
60/****************** NNTPProtocol ************************/
61
62NNTPProtocol::NNTPProtocol ( const QByteArray & pool, const QByteArray & app, bool isSSL )
63 : TCPSlaveBase((isSSL ? "nntps" : "nntp"), pool, app, isSSL ),
64 isAuthenticated( false )
65{
66 DBG << "=============> NNTPProtocol::NNTPProtocol";
67
68 readBufferLen = 0;
69 m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT;
70 m_port = m_defaultPort;
71}
72
73NNTPProtocol::~NNTPProtocol() {
74 DBG << "<============= NNTPProtocol::~NNTPProtocol";
75
76 // close connection
77 nntp_close();
78}
79
80void NNTPProtocol::setHost ( const QString & host, quint16 port, const QString & user,
81 const QString & pass )
82{
83 DBG << ( ! user.isEmpty() ? (user+'@') : QString(""))
84 << host << ":" << ( ( port == 0 ) ? m_defaultPort : port );
85
86 if ( isConnected() && (mHost != host || m_port != port ||
87 mUser != user || mPass != pass) )
88 nntp_close();
89
90 mHost = host;
91 m_port = ( ( port == 0 ) ? m_defaultPort : port );
92 mUser = user;
93 mPass = pass;
94}
95
96void NNTPProtocol::get( const KUrl& url )
97{
98 DBG << url.prettyUrl();
99 QString path = QDir::cleanPath(url.path());
100
101 // path should be like: /group/<msg_id> or /group/<serial number>
102 if ( path.startsWith( '/' ) )
103 path.remove( 0, 1 );
104 int pos = path.indexOf( '/' );
105 QString group;
106 QString msg_id;
107 if ( pos > 0 ) {
108 group = path.left( pos );
109 msg_id = path.mid( pos + 1 );
110 }
111
112 if ( group.isEmpty() || msg_id.isEmpty() ) {
113 error(ERR_DOES_NOT_EXIST,path);
114 return;
115 }
116
117 int res_code;
118 DBG << "group:" << group << "msg:" << msg_id;
119
120 if ( !nntp_open() )
121 return;
122
123 // select group if necessary
124 if ( mCurrentGroup != group && !group.isEmpty() ) {
125 infoMessage( i18n("Selecting group %1...", group ) );
126 res_code = sendCommand( "GROUP " + group );
127 if ( res_code == 411 ){
128 error( ERR_DOES_NOT_EXIST, path );
129 mCurrentGroup.clear();
130 return;
131 } else if ( res_code != 211 ) {
132 unexpected_response( res_code, "GROUP" );
133 mCurrentGroup.clear();
134 return;
135 }
136 mCurrentGroup = group;
137 }
138
139 // get article
140 infoMessage( i18n("Downloading article...") );
141 res_code = sendCommand( "ARTICLE " + msg_id );
142 if ( res_code == 423 || res_code == 430 ) {
143 error( ERR_DOES_NOT_EXIST, path );
144 return;
145 } else if (res_code != 220) {
146 unexpected_response(res_code,"ARTICLE");
147 return;
148 }
149
150 // read and send data
151 char tmp[MAX_PACKET_LEN];
152 while ( true ) {
153 if ( !waitForResponse( readTimeout() ) ) {
154 error( ERR_SERVER_TIMEOUT, mHost );
155 nntp_close();
156 return;
157 }
158 int len = readLine( tmp, MAX_PACKET_LEN );
159 const char* buffer = tmp;
160 if ( len <= 0 )
161 break;
162 if ( len == 3 && tmp[0] == '.' && tmp[1] == '\r' && tmp[2] == '\n')
163 break;
164 if ( len > 1 && tmp[0] == '.' && tmp[1] == '.' ) {
165 ++buffer;
166 --len;
167 }
168 data( QByteArray::fromRawData( buffer, len ) );
169 }
170
171 // end of data
172 data(QByteArray());
173
174 // finish
175 finished();
176}
177
178void NNTPProtocol::put( const KUrl &/*url*/, int /*permissions*/, KIO::JobFlags /*flags*/ )
179{
180 if ( !nntp_open() )
181 return;
182 if ( post_article() )
183 finished();
184}
185
186void NNTPProtocol::special(const QByteArray& data) {
187 // 1 = post article
188 int cmd;
189 QDataStream stream(data);
190
191 if ( !nntp_open() )
192 return;
193
194 stream >> cmd;
195 if (cmd == 1) {
196 if (post_article()) finished();
197 } else {
198 error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1", cmd));
199 }
200}
201
202bool NNTPProtocol::post_article() {
203 DBG;
204
205 // send post command
206 infoMessage( i18n("Sending article...") );
207 int res_code = sendCommand( "POST" );
208 if (res_code == 440) { // posting not allowed
209 error(ERR_WRITE_ACCESS_DENIED, mHost);
210 return false;
211 } else if (res_code != 340) { // 340: ok, send article
212 unexpected_response(res_code,"POST");
213 return false;
214 }
215
216 // send article now
217 int result;
218 bool last_chunk_had_line_ending = true;
219 do {
220 QByteArray buffer;
221 dataReq();
222 result = readData( buffer );
223 DBG << "receiving data:" << buffer;
224 // treat the buffer data
225 if ( result > 0 ) {
226 // translate "\r\n." to "\r\n.."
227 int pos = 0;
228 if ( last_chunk_had_line_ending && buffer[0] == '.' ) {
229 buffer.insert( 0, '.' );
230 pos += 2;
231 }
232 last_chunk_had_line_ending = ( buffer.endsWith( "\r\n" ) ); //krazy:exclude=strings
233 while ( (pos = buffer.indexOf( "\r\n.", pos )) > 0) {
234 buffer.insert( pos + 2, '.' );
235 pos += 4;
236 }
237
238 // send data to socket, write() doesn't send the terminating 0
239 write( buffer, buffer.length() );
240 DBG << "writing:" << buffer;
241 }
242 } while ( result > 0 );
243
244 // error occurred?
245 if (result<0) {
246 ERR << "error while getting article data for posting";
247 nntp_close();
248 return false;
249 }
250
251 // send end mark
252 write( "\r\n.\r\n", 5 );
253
254 // get answer
255 res_code = evalResponse( readBuffer, readBufferLen );
256 if (res_code == 441) { // posting failed
257 error(ERR_COULD_NOT_WRITE, mHost);
258 return false;
259 } else if (res_code != 240) {
260 unexpected_response(res_code,"POST");
261 return false;
262 }
263
264 return true;
265}
266
267
268void NNTPProtocol::stat( const KUrl& url ) {
269 DBG << url.prettyUrl();
270 UDSEntry entry;
271 QString path = QDir::cleanPath(url.path());
272 QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive);
273 QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive);
274 int pos;
275 QString group;
276 QString msg_id;
277
278 // / = group list
279 if (path.isEmpty() || path == "/") {
280 DBG << "root";
281 fillUDSEntry( entry, QString(), 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
282
283 // /group = message list
284 } else if (regGroup.indexIn(path) == 0) {
285 if ( path.startsWith( '/' ) ) path.remove(0,1);
286 if ((pos = path.indexOf('/')) > 0) group = path.left(pos);
287 else group = path;
288 DBG << "group:" << group;
289 // postingAllowed should be ored here with "group not moderated" flag
290 // as size the num of messages (GROUP cmd) could be given
291 fillUDSEntry( entry, group, 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) );
292
293 // /group/<msg_id> = message
294 } else if (regMsgId.indexIn(path) == 0) {
295 pos = path.indexOf('<');
296 group = path.left(pos);
297 msg_id = KUrl::fromPercentEncoding( path.right(path.length()-pos).toLatin1() );
298 if ( group.startsWith( '/' ) )
299 group.remove( 0, 1 );
300 if ((pos = group.indexOf('/')) > 0) group = group.left(pos);
301 DBG << "group:" << group << "msg:" << msg_id;
302 fillUDSEntry( entry, msg_id, 0, true );
303
304 // invalid url
305 } else {
306 error(ERR_DOES_NOT_EXIST,path);
307 return;
308 }
309
310 statEntry(entry);
311 finished();
312}
313
314void NNTPProtocol::listDir( const KUrl& url ) {
315 DBG << url.prettyUrl();
316 if ( !nntp_open() )
317 return;
318
319 QString path = QDir::cleanPath(url.path());
320
321 if (path.isEmpty())
322 {
323 KUrl newURL(url);
324 newURL.setPath("/");
325 DBG << "redirecting to" << newURL.prettyUrl();
326 redirection(newURL);
327 finished();
328 return;
329 }
330 else if ( path == "/" ) {
331 fetchGroups( url.queryItem( "since" ), url.queryItem( "desc" ) == "true" );
332 finished();
333 } else {
334 // if path = /group
335 int pos;
336 QString group;
337 if ( path.startsWith( '/' ) )
338 path.remove( 0, 1 );
339 if ((pos = path.indexOf('/')) > 0)
340 group = path.left(pos);
341 else
342 group = path;
343 QString first = url.queryItem( "first" );
344 QString max = url.queryItem( "max" );
345 if ( fetchGroup( group, first.toULong(), max.toULong() ) )
346 finished();
347 }
348}
349
350void NNTPProtocol::fetchGroups( const QString &since, bool desc )
351{
352 int expected;
353 int res;
354 if ( since.isEmpty() ) {
355 // full listing
356 infoMessage( i18n("Downloading group list...") );
357 res = sendCommand( "LIST" );
358 expected = 215;
359 } else {
360 // incremental listing
361 infoMessage( i18n("Looking for new groups...") );
362 res = sendCommand( "NEWGROUPS " + since );
363 expected = 231;
364 }
365 if ( res != expected ) {
366 unexpected_response( res, "LIST" );
367 return;
368 }
369
370 // read newsgroups line by line
371 QByteArray line;
372 QString group;
373 int pos, pos2;
374 long msg_cnt;
375 long access;
376 UDSEntry entry;
377 QHash<QString, UDSEntry> entryMap;
378
379 // read in data and process each group. one line at a time
380 while ( true ) {
381 if ( ! waitForResponse( readTimeout() ) ) {
382 error( ERR_SERVER_TIMEOUT, mHost );
383 nntp_close();
384 return;
385 }
386 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
387 line = QByteArray( readBuffer, readBufferLen );
388 if ( line == ".\r\n" )
389 break;
390
391 // group name
392 if ((pos = line.indexOf(' ')) > 0) {
393
394 group = line.left(pos);
395
396 // number of messages
397 line.remove(0,pos+1);
398 long last = 0;
399 access = 0;
400 if (((pos = line.indexOf(' ')) > 0 || (pos = line.indexOf('\t')) > 0) &&
401 ((pos2 = line.indexOf(' ',pos+1)) > 0 || (pos2 = line.indexOf('\t',pos+1)) > 0)) {
402 last = line.left(pos).toLongLong();
403 long first = line.mid(pos+1,pos2-pos-1).toLongLong();
404 msg_cnt = abs(last-first+1);
405 // group access rights
406 switch ( line[pos2 + 1] ) {
407 case 'n': access = 0; break;
408 case 'm': access = S_IWUSR | S_IWGRP; break;
409 case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH; break;
410 }
411 } else {
412 msg_cnt = 0;
413 }
414
415 entry.clear();
416 fillUDSEntry( entry, group, msg_cnt, false, access );
417 if ( !desc )
418 listEntry( entry, false );
419 else
420 entryMap.insert( group, entry );
421 }
422 }
423
424 // handle group descriptions
425 QHash<QString, UDSEntry>::Iterator it = entryMap.begin();
426 if ( desc ) {
427 infoMessage( i18n("Downloading group descriptions...") );
428 totalSize( entryMap.size() );
429 }
430 while ( desc ) {
431 // request all group descriptions
432 if ( since.isEmpty() )
433 res = sendCommand( "LIST NEWSGROUPS" );
434 else {
435 // request only descriptions for new groups
436 if ( it == entryMap.end() )
437 break;
438 res = sendCommand( "LIST NEWSGROUPS " + it.key() );
439 ++it;
440 if( res == 503 ) {
441 // Information not available (RFC 2980 §2.1.6), try next group
442 continue;
443 }
444 }
445 if ( res != 215 ) {
446 // No group description available or not implemented
447 break;
448 }
449
450 // download group descriptions
451 while ( true ) {
452 if ( ! waitForResponse( readTimeout() ) ) {
453 error( ERR_SERVER_TIMEOUT, mHost );
454 nntp_close();
455 return;
456 }
457 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
458 line = QByteArray( readBuffer, readBufferLen );
459 if ( line == ".\r\n" )
460 break;
461
462 //DBG << " fetching group description: " << QString( line ).trimmed();
463 int pos = line.indexOf( ' ' );
464 pos = pos < 0 ? line.indexOf( '\t' ) : qMin( pos, line.indexOf( '\t' ) );
465 group = line.left( pos );
466 QString groupDesc = line.right( line.length() - pos ).trimmed();
467
468 if ( entryMap.contains( group ) ) {
469 entry = entryMap.take( group );
470 entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc );
471 listEntry( entry, false );
472 }
473 }
474
475 if ( since.isEmpty() )
476 break;
477 }
478 // take care of groups without descriptions
479 for ( QHash<QString, UDSEntry>::Iterator it = entryMap.begin(); it != entryMap.end(); ++it )
480 listEntry( it.value(), false );
481
482 entry.clear();
483 listEntry( entry, true );
484}
485
486bool NNTPProtocol::fetchGroup( QString &group, unsigned long first, unsigned long max ) {
487 int res_code;
488 QString resp_line;
489
490 // select group
491 infoMessage( i18n("Selecting group %1...", group ) );
492 res_code = sendCommand( "GROUP " + group );
493 if ( res_code == 411 ) {
494 error( ERR_DOES_NOT_EXIST, group );
495 mCurrentGroup.clear();
496 return false;
497 } else if ( res_code != 211 ) {
498 unexpected_response( res_code, "GROUP" );
499 mCurrentGroup.clear();
500 return false;
501 }
502 mCurrentGroup = group;
503
504 // repsonse to "GROUP <requested-group>" command is 211 then find the message count (cnt)
505 // and the first and last message followed by the group name
506 unsigned long firstSerNum, lastSerNum;
507 resp_line = QString::fromLatin1( readBuffer );
508 QRegExp re ( "211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)");
509 if ( re.indexIn( resp_line ) != -1 ) {
510 firstSerNum = re.cap( 2 ).toLong();
511 lastSerNum = re.cap( 3 ).toLong();
512 } else {
513 error( ERR_INTERNAL, i18n("Could not extract message serial numbers from server response:\n%1",
514 resp_line ) );
515 return false;
516 }
517
518 if (firstSerNum == 0)
519 return true;
520 first = qMax( first, firstSerNum );
521 if ( lastSerNum < first ) { // No need to fetch anything
522 // note: this also ensure that "lastSerNum - first" is not negative
523 // in the next test (in "unsigned long" computation this leads to an overflow
524 return true;
525 }
526 if ( max > 0 && lastSerNum - first > max )
527 first = lastSerNum - max + 1;
528
529 DBG << "Starting from serial number: " << first << " of " << firstSerNum << " - " << lastSerNum;
530 setMetaData( "FirstSerialNumber", QString::number( firstSerNum ) );
531 setMetaData( "LastSerialNumber", QString::number( lastSerNum ) );
532
533 infoMessage( i18n("Downloading new headers...") );
534 totalSize( lastSerNum - first );
535 bool notSupported = true;
536 if ( fetchGroupXOVER( first, notSupported ) )
537 return true;
538 else if ( notSupported )
539 return fetchGroupRFC977( first );
540 return false;
541}
542
543
544bool NNTPProtocol::fetchGroupRFC977( unsigned long first )
545{
546 UDSEntry entry;
547
548 // set article pointer to first article and get msg-id of it
549 int res_code = sendCommand( "STAT " + QString::number( first ) );
550 QString resp_line = readBuffer;
551 if (res_code != 223) {
552 unexpected_response(res_code,"STAT");
553 return false;
554 }
555
556 //STAT res_line: 223 nnn <msg_id> ...
557 QString msg_id;
558 int pos, pos2;
559 if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) {
560 msg_id = resp_line.mid(pos,pos2-pos+1);
561 fillUDSEntry( entry, msg_id, 0, true );
562 listEntry( entry, false );
563 } else {
564 error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1",
565 resp_line));
566 return false;
567 }
568
569 // go through all articles
570 while (true) {
571 res_code = sendCommand("NEXT");
572 if (res_code == 421) {
573 // last artice reached
574 entry.clear();
575 listEntry( entry, true );
576 return true;
577 } else if (res_code != 223) {
578 unexpected_response(res_code,"NEXT");
579 return false;
580 }
581
582 //res_line: 223 nnn <msg_id> ...
583 resp_line = readBuffer;
584 if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) {
585 msg_id = resp_line.mid(pos,pos2-pos+1);
586 entry.clear();
587 fillUDSEntry( entry, msg_id, 0, true );
588 listEntry( entry, false );
589 } else {
590 error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1",
591 resp_line));
592 return false;
593 }
594 }
595 return true; // Not reached
596}
597
598
599bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool &notSupported )
600{
601 notSupported = false;
602
603 QString line;
604 QStringList headers;
605
606 int res = sendCommand( "LIST OVERVIEW.FMT" );
607 if ( res == 215 ) {
608 while ( true ) {
609 if ( ! waitForResponse( readTimeout() ) ) {
610 error( ERR_SERVER_TIMEOUT, mHost );
611 nntp_close();
612 return false;
613 }
614 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
615 line = QString::fromLatin1( readBuffer, readBufferLen );
616 if ( line == ".\r\n" )
617 break;
618 headers << line.trimmed();
619 DBG << "OVERVIEW.FMT:" << line.trimmed();
620 }
621 } else {
622 // fallback to defaults
623 headers << "Subject:" << "From:" << "Date:" << "Message-ID:"
624 << "References:" << "Bytes:" << "Lines:";
625 }
626
627 res = sendCommand( "XOVER " + QString::number( first ) + '-' );
628 if ( res == 420 )
629 return true; // no articles selected
630 if ( res == 500 )
631 notSupported = true; // unknwon command
632 if ( res != 224 ) {
633 unexpected_response( res, "XOVER" );
634 return false;
635 }
636
637 long msgSize;
638 QString name;
639 UDSEntry entry;
640 int udsType;
641
642 QStringList fields;
643 while ( true ) {
644 if ( ! waitForResponse( readTimeout() ) ) {
645 error( ERR_SERVER_TIMEOUT, mHost );
646 nntp_close();
647 return false;
648 }
649 readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN );
650 line = QString::fromLatin1( readBuffer, readBufferLen );
651 if ( line == ".\r\n" ) {
652 entry.clear();
653 listEntry( entry, true );
654 return true;
655 }
656
657 fields = line.split( '\t', QString::KeepEmptyParts);
658 msgSize = 0;
659 entry.clear();
660 udsType = KIO::UDSEntry::UDS_EXTRA;
661 QStringList::ConstIterator it = headers.constBegin();
662 QStringList::ConstIterator it2 = fields.constBegin();
663 // first entry is the serial number
664 name = (*it2);
665 ++it2;
666 for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) {
667 if ( (*it) == "Bytes:" ) {
668 msgSize = (*it2).toLong();
669 continue;
670 }
671 QString atomStr;
672 if ( (*it).endsWith( QLatin1String( "full" ) ) )
673 if ( (*it2).trimmed().isEmpty() )
674 atomStr = (*it).left( (*it).indexOf( ':' ) + 1 ); // strip of the 'full' suffix
675 else
676 atomStr = (*it2).trimmed();
677 else
678 atomStr = (*it) + ' ' + (*it2).trimmed();
679 entry.insert( udsType++, atomStr );
680 if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END )
681 break;
682 }
683 fillUDSEntry( entry, name, msgSize, true );
684 listEntry( entry, false );
685 }
686 return true; // not reached
687}
688
689
690void NNTPProtocol::fillUDSEntry( UDSEntry& entry, const QString& name, long size,
691 bool is_article, long access ) {
692
693 long posting=0;
694
695 // entry name
696 entry.insert(KIO::UDSEntry::UDS_NAME, name);
697
698 // entry size
699 entry.insert(KIO::UDSEntry::UDS_SIZE, size);
700
701 // file type
702 entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR);
703
704 // access permissions
705 posting = postingAllowed? access : 0;
706 long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) :
707 (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting);
708 entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal);
709
710 entry.insert(KIO::UDSEntry::UDS_USER, mUser.isEmpty() ? QString::fromLatin1("root") : mUser);
711
712 /*
713 entry->insert(UDS_GROUP, QString::fromLatin1("root"));
714 */
715
716 // MIME type
717 if (is_article) {
718 entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/news") );
719 }
720}
721
722void NNTPProtocol::nntp_close () {
723 if ( isConnected() ) {
724 write( "QUIT\r\n", 6 );
725 disconnectFromHost();
726 isAuthenticated = false;
727 }
728 mCurrentGroup.clear();
729}
730
731bool NNTPProtocol::nntp_open()
732{
733 // if still connected reuse connection
734 if ( isConnected() ) {
735 DBG << "reusing old connection";
736 return true;
737 }
738
739 DBG << " nntp_open -- creating a new connection to" << mHost << ":" << m_port;
740 // create a new connection (connectToHost() includes error handling)
741 infoMessage( i18n("Connecting to server...") );
742 if ( connectToHost( (isAutoSsl() ? "nntps" : "nntp"), mHost, m_port ) )
743 {
744 DBG << " nntp_open -- connection is open";
745
746 // read greeting
747 int res_code = evalResponse( readBuffer, readBufferLen );
748
749 /* expect one of
750 200 server ready - posting allowed
751 201 server ready - no posting allowed
752 */
753 if ( ! ( res_code == 200 || res_code == 201 ) )
754 {
755 unexpected_response(res_code,"CONNECT");
756 return false;
757 }
758
759 DBG << " nntp_open -- greating was read res_code :" << res_code;
760
761 res_code = sendCommand("MODE READER");
762
763 // TODO: not in RFC 977, so we should not abort here
764 if ( !(res_code == 200 || res_code == 201) ) {
765 unexpected_response( res_code, "MODE READER" );
766 return false;
767 }
768
769 // let local class know whether posting is allowed or not
770 postingAllowed = (res_code == 200);
771
772 // activate TLS if requested
773 if ( metaData("tls") == "on" ) {
774 if ( sendCommand( "STARTTLS" ) != 382 ) {
775 error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") );
776 return false;
777 }
778 if ( !startSsl() ) {
779 error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") );
780 return false;
781 }
782 }
783
784 // *try* to authenticate now (see bug#167718)
785 authenticate();
786
787 return true;
788 }
789
790 return false;
791}
792
793int NNTPProtocol::sendCommand( const QString &cmd )
794{
795 int res_code = 0;
796
797 if ( !nntp_open() ) {
798 ERR << "NOT CONNECTED, cannot send cmd" << cmd;
799 return 0;
800 }
801
802 DBG << "cmd:" << cmd;
803
804 write( cmd.toLatin1(), cmd.length() );
805 // check the command for proper termination
806 if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) )
807 write( "\r\n", 2 );
808 res_code = evalResponse( readBuffer, readBufferLen );
809
810 // if authorization needed send user info
811 if (res_code == 480) {
812 DBG << "auth needed, sending user info";
813
814 if ( mUser.isEmpty() || mPass.isEmpty() ) {
815 KIO::AuthInfo authInfo;
816 authInfo.username = mUser;
817 authInfo.password = mPass;
818 if ( openPasswordDialog( authInfo ) ) {
819 mUser = authInfo.username;
820 mPass = authInfo.password;
821 }
822 }
823 if ( mUser.isEmpty() || mPass.isEmpty() )
824 return res_code;
825
826 res_code = authenticate();
827 if (res_code != 281) {
828 // error should be handled by invoking function
829 return res_code;
830 }
831
832 // ok now, resend command
833 write( cmd.toLatin1(), cmd.length() );
834 if ( !cmd.endsWith( QLatin1String( "\r\n" ) ) )
835 write( "\r\n", 2 );
836 res_code = evalResponse( readBuffer, readBufferLen );
837 }
838
839 return res_code;
840}
841
842int NNTPProtocol::authenticate()
843{
844 int res_code = 0;
845
846 if( isAuthenticated ) {
847 // already authenticated
848 return 281;
849 }
850
851 if( mUser.isEmpty() || mPass.isEmpty() ) {
852 return 281; // failsafe : maybe add a "relax" mode to optionally ask user/pwd.
853 }
854
855 // send username to server and confirm response
856 write( "AUTHINFO USER ", 14 );
857 write( mUser.toLatin1(), mUser.length() );
858 write( "\r\n", 2 );
859 res_code = evalResponse( readBuffer, readBufferLen );
860
861 if( res_code == 281 ) {
862 // no password needed (RFC 2980 3.1.1 does not required one)
863 return res_code;
864 }
865 if (res_code != 381) {
866 // error should be handled by invoking function
867 return res_code;
868 }
869
870 // send password
871 write( "AUTHINFO PASS ", 14 );
872 write( mPass.toLatin1(), mPass.length() );
873 write( "\r\n", 2 );
874 res_code = evalResponse( readBuffer, readBufferLen );
875
876 if( res_code == 281 ) {
877 isAuthenticated = true;
878 }
879
880 return res_code;
881}
882
883void NNTPProtocol::unexpected_response( int res_code, const QString &command )
884{
885 ERR << "Unexpected response to" << command << "command: (" << res_code << ")"
886 << readBuffer;
887
888 // See RFC 3977 appendix C "Summary of Response Codes"
889 switch ( res_code ) {
890 case 205: // connection closed by the server: this can happens, e.g. if the session timeout on the server side
891 // Not the same thing, but use the same message as code 400 anyway.
892 case 400: // temporary issue on the server
893 error( ERR_INTERNAL_SERVER,
894 i18n( "The server %1 could not handle your request.\n"
895 "Please try again now, or later if the problem persists.", mHost ) );
896 break;
897 case 480: // credential request
898 error( ERR_COULD_NOT_LOGIN,
899 i18n( "You need to authenticate to access the requested resource." ) );
900 break;
901 case 481: // wrong credential (TODO: place a specific message for this case)
902 error( ERR_COULD_NOT_LOGIN,
903 i18n( "The supplied login and/or password are incorrect." ) );
904 break;
905 case 502:
906 error( ERR_ACCESS_DENIED, mHost );
907 break;
908 default:
909 error( ERR_INTERNAL, i18n( "Unexpected server response to %1 command:\n%2", command, readBuffer ) );
910 }
911
912 nntp_close();
913}
914
915int NNTPProtocol::evalResponse ( char *data, ssize_t &len )
916{
917 if ( !waitForResponse( responseTimeout() ) ) {
918 error( ERR_SERVER_TIMEOUT , mHost );
919 nntp_close();
920 return -1;
921 }
922 len = readLine( data, MAX_PACKET_LEN );
923
924 if ( len < 3 )
925 return -1;
926
927 // get the first three characters. should be the response code
928 int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) );
929
930 DBG << "got:" << respCode;
931
932 return respCode;
933}
934
935/* not really necessary, because the slave has to
936 use the KIO::Error's instead, but let this here for
937 documentation of the NNTP response codes and may
938 by later use.
939QString& NNTPProtocol::errorStr(int resp_code) {
940 QString ret;
941
942 switch (resp_code) {
943 case 100: ret = "help text follows"; break;
944 case 199: ret = "debug output"; break;
945
946 case 200: ret = "server ready - posting allowed"; break;
947 case 201: ret = "server ready - no posting allowed"; break;
948 case 202: ret = "slave status noted"; break;
949 case 205: ret = "closing connection - goodbye!"; break;
950 case 211: ret = "group selected"; break;
951 case 215: ret = "list of newsgroups follows"; break;
952 case 220: ret = "article retrieved - head and body follow"; break;
953 case 221: ret = "article retrieved - head follows"; break;
954 case 222: ret = "article retrieved - body follows"; break;
955 case 223: ret = "article retrieved - request text separately"; break;
956 case 230: ret = "list of new articles by message-id follows"; break;
957 case 231: ret = "list of new newsgroups follows"; break;
958 case 235: ret = "article transferred ok"; break;
959 case 240: ret = "article posted ok"; break;
960
961 case 335: ret = "send article to be transferred"; break;
962 case 340: ret = "send article to be posted"; break;
963
964 case 400: ret = "service discontinued"; break;
965 case 411: ret = "no such news group"; break;
966 case 412: ret = "no newsgroup has been selected"; break;
967 case 420: ret = "no current article has been selected"; break;
968 case 421: ret = "no next article in this group"; break;
969 case 422: ret = "no previous article in this group"; break;
970 case 423: ret = "no such article number in this group"; break;
971 case 430: ret = "no such article found"; break;
972 case 435: ret = "article not wanted - do not send it"; break;
973 case 436: ret = "transfer failed - try again later"; break;
974 case 437: ret = "article rejected - do not try again"; break;
975 case 440: ret = "posting not allowed"; break;
976 case 441: ret = "posting failed"; break;
977
978 case 500: ret = "command not recognized"; break;
979 case 501: ret = "command syntax error"; break;
980 case 502: ret = "access restriction or permission denied"; break;
981 case 503: ret = "program fault - command not performed"; break;
982 default: ret = QString("unknown NNTP response code %1").arg(resp_code);
983 }
984
985 return ret;
986}
987*/
NNTPProtocol
NNTP KIO slave.
Definition nntp.h:34
NNTPProtocol::special
virtual void special(const QByteArray &data)
Special command: 1 = post article it takes no other args, the article data are requested by dataReq()...
Definition nntp.cpp:186
NNTPProtocol::nntp_open
bool nntp_open()
Attempt to initiate a NNTP connection via a TCP socket, if no existing connection could be reused.
Definition nntp.cpp:731
NNTPProtocol::nntp_close
void nntp_close()
Attempt to properly shut down the NNTP connection by sending "QUIT\r\n" before closing the socket.
Definition nntp.cpp:722
NNTPProtocol::sendCommand
int sendCommand(const QString &cmd)
Send a command to the server.
Definition nntp.cpp:793
NNTPProtocol::NNTPProtocol
NNTPProtocol(const QByteArray &pool, const QByteArray &app, bool isSSL)
Default Constructor.
Definition nntp.cpp:62
NNTPProtocol::post_article
bool post_article()
Post article.
Definition nntp.cpp:202
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Wed Jan 24 2024 00:00:00 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kioslave/nntp

Skip menu "kioslave/nntp"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal