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

kioslave/nntp

  • sources
  • kde-4.14
  • kdepimlibs
  • 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 
33 using namespace KIO;
34 
35 extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
36 
37 int 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 
62 NNTPProtocol::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 
73 NNTPProtocol::~NNTPProtocol() {
74  DBG << "<============= NNTPProtocol::~NNTPProtocol";
75 
76  // close connection
77  nntp_close();
78 }
79 
80 void 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 
96 void 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 
178 void 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 
186 void 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 
202 bool 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 
268 void 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 
314 void 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 
350 void 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 
486 bool 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 
544 bool 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 
599 bool 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 
690 void 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 
722 void NNTPProtocol::nntp_close () {
723  if ( isConnected() ) {
724  write( "QUIT\r\n", 6 );
725  disconnectFromHost();
726  isAuthenticated = false;
727  }
728  mCurrentGroup.clear();
729 }
730 
731 bool 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 
793 int 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 
842 int 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 
883 void 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 
915 int 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.
939 QString& 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 */
QList::clear
void clear()
QString::toULong
ulong toULong(bool *ok, int base) const
QString::indexOf
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QHash::insert
iterator insert(const Key &key, const T &value)
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
QHash::key
const Key key(const T &value) const
QByteArray
QDataStream
QString::split
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QHash::size
int size() const
QByteArray::fromRawData
QByteArray fromRawData(const char *data, int size)
QByteArray::insert
QByteArray & insert(int i, char ch)
QByteArray::length
int length() const
QString::remove
QString & remove(int position, int n)
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::post_article
bool post_article()
Post article.
Definition: nntp.cpp:202
QString::clear
void clear()
QRegExp::indexIn
int indexIn(const QString &str, int offset, CaretMode caretMode) const
QRegExp
QByteArray::indexOf
int indexOf(char ch, int from) const
QString::number
QString number(int n, int base)
QString::insert
QString & insert(int position, QChar ch)
QHash
QString::isEmpty
bool isEmpty() const
QString::trimmed
QString trimmed() const
QHash::begin
iterator begin()
QString::startsWith
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QByteArray::right
QByteArray right(int len) const
NNTPProtocol::sendCommand
int sendCommand(const QString &cmd)
Send a command to the server.
Definition: nntp.cpp:793
QString
QByteArray::mid
QByteArray mid(int pos, int len) const
QStringList
QString::right
QString right(int n) const
NNTPProtocol::NNTPProtocol
NNTPProtocol(const QByteArray &pool, const QByteArray &app, bool isSSL)
Default Constructor.
Definition: nntp.cpp:62
QByteArray::toLongLong
qlonglong toLongLong(bool *ok, int base) const
NNTPProtocol
NNTP KIO slave.
Definition: nntp.h:33
QString::toLong
long toLong(bool *ok, int base) const
QDir::cleanPath
QString cleanPath(const QString &path)
QByteArray::left
QByteArray left(int len) const
QString::toLatin1
QByteArray toLatin1() const
QString::mid
QString mid(int position, int n) const
QHash::take
T take(const Key &key)
QLatin1String
QList::ConstIterator
typedef ConstIterator
QString::length
int length() const
QString::left
QString left(int n) const
QString::fromLatin1
QString fromLatin1(const char *str, int size)
QHash::contains
bool contains(const Key &key) const
QHash::end
iterator end()
QList::constEnd
const_iterator constEnd() const
QList::constBegin
const_iterator constBegin() const
QByteArray::remove
QByteArray & remove(int pos, int len)
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
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:37:13 by doxygen 1.8.7 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 API Reference

Skip menu "kdepimlibs 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

Search



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