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

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