nntp._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * $Id: nntp.pike,v 1.2 2009/08/07 15:22:36 nicke Exp $
18  */
19 inherit "/net/coal/login";
20 inherit "/net/base/line";
21 #include <macros.h>
22 #include <config.h>
23 #include <database.h>
24 class nntp : public login,line{
25 public:
26 
27 
28 
29 
30 
31 #define NNTP_DEBUG
32 
33 #ifdef NNTP_DEBUG
34 #define NNTP_LOG(s, args...) werror(s +"\n", args);
35 #else
36 #define NNTP_LOG(s, args...)
37 #endif
38 
39 #define CHECK_GROUP if ( !objectp(oCurrentGroup) ) {\
40  send_response(412);\
41  return;\
42 }
43 
44 #define CHECK_ARTICLE if ( !objectp(oCurrentArticle) ) {\
45  send_response(420);\
46  return;\
47 }
48 
49 #define MODE_READ 1
50 #define MODE_POST 2
51 
52  string sPost = "";
53  int iMode = MODE_READ;
54 
55 protected:
56  mapping mResponses = ([
57  100: "help text follows",
58  199: "debug output",
59  200: "sTeam news server ready - posting allowed",
60  201: "sTeam news server ready - no posting allowed",
61  202: "slave status noted",
62  205: "closed connection - goodbye!",
63  211: "n f l s group selected",
64  215: "list of newsgroups follows",
65  220: "n <a> article retrieved - head and body follow",
66  221: "n <a> article retrieved - head follows",
67  222: "n <a> article retrieved - body follows",
68  223: "n <a> article retrieved - request text separately",
69  224: "Overview information follows",
70  230: "list of new articles by message id follows",
71  231: "list of new newsgroups follows",
72  235: "article transferred ok",
73  240: "article posted ok",
74  281: "Authentication accepted",
75  335: "send article to be transferred. End with <CR-LF>.<CR-LF>",
76  340: "send article to be posted. End with <CR-LF>.<CR-LF>",
77  381: "More authentication information required",
78  400: "service discontinued",
79  411: "no such news group",
80  412: "no newsgroup has been selected",
81  420: "no current article has been selected",
82  421: "no next article in this group",
83  423: "no such article number in this group",
84  430: "no such article found",
85  440: "posting not allowed",
86  441: "posting failed",
87  480: "Authentication required",
88  482: "Authentication rejected",
89  500: "Command not recognized",
90  501: "command syntax error",
91  502: "No permission",
92  503: "program fault - command not performed",
93  ]);
94 
95  object oGroups = 0;
96  object oCurrentGroup = 0;
97  object oCurrentArticle = 0;
98 
99 /**
100  * Send a list of groups.
101  *
102  * @param void|string type - send subscribed groups or overview.fmt
103  */
104 protected:
105  void list_groups(void|string type)
106 {
107  if ( zero_type(type) || lower_case(type) == "subscriptions" ) {
108  send_response(215);
109  array groups = oGroups->list_groups();
110 
111 
112  if ( arrayp(groups) ) {
113  foreach( groups, object grp ) {
114  send_result(grp->get_name(),
115  (string)grp->get_last_message(),
116  (string)grp->get_first_message(),
117  (grp->can_post()?"y":"n"));
118  }
119  }
120  send_message(".\r\n");
121  return;
122  }
123  if ( lower_case(type) == "overview.fmt" )
124  {
125  send_result("215", "Order of Fields in Overview database.");
126  send_message("Subject: \r\n");
127  send_message("From: \r\n");
128  send_message("Date: \r\n");
129  send_message("Message-ID: \r\n");
130  send_message("References: \r\n");
131  send_message("Bytes: \r\n");
132  send_message("Lines: \r\n");
133  send_message("Xref:full \r\n");
134  send_message(".\r\n");
135  return;
136  }
137  send_response(503);
138 }
139 
140 public:
141 
142 /**
143  * Send a list of new groups.
144  *
145  * @param string date - last newsgroup check date.
146  * @param string t - time of last check.
147  */
148 
149 protected:
150  void new_groups(string date, string t)
151 {
152  send_result("231", "New newsgroups follow");
153  array groups = oGroups->list_groups();
154 
155  if ( arrayp(groups) ) {
156  foreach(groups, object grp) {
157  // FIXME! Check if newer
158  send_result(grp->get_name());
159  }
160  }
161  send_result(".");
162 }
163 
164 public:
165 
166 /**
167  * Get a list of new groups newer than date, time t.
168  *
169  * @param int date - date of last check
170  * @param int t - time of last check.
171  */
172 protected:
173  void list_new_groups(int date, int t)
174 {
175  send_response(231);
176  array groups = oGroups->list_groups();
177 
178  foreach( groups, object grp ) {
179  if ( grp->newer_than(date, t) )
180  send_result(grp->get_name(),
181  (string)grp->get_last_message(),
182  (string)grp->get_first_message(),
183  (grp->can_post()?"y":"n"));
184  }
185  send_message(".\r\n");
186 }
187 
188 public:
189 
190 /**
191  * Get a list of new news entries newer than date, time t.
192  *
193  * @param int date - date of last check
194  * @param int t - time of last check.
195  */
196 protected:
197  void new_news(string newsgroups, string|int date, string|int t){
198  //FIXME this is work in progress...
199  // this is not finished - so stop processing
200  send_response(500);
201 
202  // Todo '*' wildcard
203  // Todo ',' separated groupsnames
204  // Check 512 character command length limit
205 
206  int idate;
207  int itime;
208  if( stringp(date) ) {
209  // Format is either YYMMDD with YY rounded to the nearest century (RFC 977)
210  // or YYYYMMDD
211  if( sizeof(date)==8 ){
212  sscanf(date, "%4d%2d%2d", int year,int month,int day);
213  Calendar.Day(year,month,day);
214  }else {
215  NNTP_LOG("Got unsupported/bad date format '" + date + "'");
216  send_response(500);
217  }
218  idate=(int)date;
219  }
220  if( stringp(t) ) {
221  itime=(int)t;
222  }
223  // FIXME TODO
224  // send
225 }
226 
227 public:
228 
229 /**
230  * Select an article from the current group.
231  *
232  * @param string num - number of the article.
233  * @return object of selected article.
234  */
235 protected:
236  object select_article(string num)
237 {
238  int art_num;
239 
240  if ( sscanf(num, "<%d@%*s", art_num) == 0 ) {
241  art_num = (int)num;
242  // RFC says article pointer is set by article (numeric)
243  oCurrentArticle = oCurrentGroup->get_article(art_num);
244  return oCurrentArticle;
245  }
246  return find_object(art_num);
247 }
248 
249 public:
250 
251 
252 /**
253  * Select the article with id 'id' and send status of it.
254  *
255  * @param string id - id of article
256  */
257 protected:
258  void stat(string id)
259 {
260  if ( !objectp(oCurrentGroup) ) {
261  send_response(412);
262  return;
263  }
264  object article = select_article(id);
265  send_result("223", (string)oCurrentGroup->get_article_num(article),
266  oCurrentGroup->message_id(article),
267  "article retrieved - request text separately");
268 }
269 
270 public:
271 
272 /**
273  * Set the next article as current.
274  *
275  */
276 protected:
277  void next()
278 {
279  CHECK_GROUP;
280  CHECK_ARTICLE;
281 
282  object article = oCurrentGroup->get_next_article(oCurrentArticle);
283  oCurrentArticle = article;
284  send_result("223", (string)oCurrentGroup->get_article_num(article),
285  oCurrentGroup->message_id(article),
286  "article retrieved - request text separately");
287 }
288 
289 public:
290 
291 
292 /**
293  * XOVER command sends article overview.
294  *
295  * @param string|void range
296  */
297 protected:
298  void xover(string|void range)
299 {
300  if ( !objectp(oCurrentGroup) ) {
301  send_response(412);
302  return;
303  }
304  NNTP_LOG("xover range=%O\n", range);
305 
306  int from, to;
307  if ( stringp(range) ) {
308  if ( sscanf(range, "%d-%d", from, to) != 2 ) {
309  if ( sscanf(range, "%d-", from) == 1 ) {
310  to = 0xffffffff;
311  }
312  else if ( sscanf(range, "%d", from ) == 1 ) {
313  to = from;
314  }
315  else
316  send_response(420);
317 
318  }
319  }
320  else {
321  if ( oCurrentArticle )
322  from = oCurrentGroup->get_article_num(oCurrentArticle);
323  else
324  from = oCurrentGroup->get_first_message();
325  to = from; // no argument given - use current message
326  }
327  array articles = oCurrentGroup->get_articles();
328  array inRange = ({ });
329  object art;
330 
331  foreach( articles, art ) {
332  int oid = oCurrentGroup->get_article_num(art);
333  if ( oid >= from && oid <= to )
334  inRange += ({ art });
335  }
336  if ( sizeof(inRange) > 0 ) {
337  send_response(224);
338  foreach(inRange, art) {
339  send_message(oCurrentGroup->header(art) + "\r\n");
340  }
341  }
342  else {
343  send_response(420);
344  }
345 
346  send_message(".\r\n");
347 }
348 
349 public:
350 
351 /**
352  * Select the group 'grp' as current group.
353  *
354  * @param string grp - name of the group to select.
355  */
356 protected:
357  void group(string grp)
358 {
359  object group = oGroups->get_group(grp);
360  if ( !objectp(group) ) {
361  send_response(411);
362  return;
363  }
364  else if ( !group->read_access(oUser) ) {
365  if ( oUser == _GUEST )
366  send_response(480);
367  else
368  send_response(411); // is this correct ? no such newsgroup ???
369  return;
370  }
371 
372  oCurrentGroup = group;
373  send_result((string)211, (string)group->get_num_messages(),
374  (string)group->get_first_message(),
375  (string)group->get_last_message(),
376  group->get_name(),
377  "Newsgroup selected");
378 }
379 
380 public:
381 
382 /**
383  * Set the mode to new mode 'm'. Just sends back 200 response.
384  *
385  * @param string m - the new mode.
386  */
387 protected:
388  void mode(string m)
389 {
390  send_result("200","Hello, you can post");
391 }
392 
393 public:
394 
395 /**
396  * Authorize with user or password.
397  *
398  * @param string subcmd - authorization method, only 'user' and 'pass' allowed.
399  * @param string auth - auth parameter.
400  */
401 protected:
402  void authorize(string subcmd, string auth)
403 {
404  LOG("authorize("+subcmd+","+auth+")");
405  switch ( lower_case(subcmd) ) {
406  case "user":
407  oUser = _Persistence->lookup_user(auth);
408  LOG("User="+sprintf("%O", oUser));
409  if ( objectp(oUser) )
410  send_response(381);
411  else
412  send_response(482);
413  break;
414  case "pass":
415  if ( !objectp(oUser) )
416  send_response(381);
417  if ( oUser->check_user_password(auth) ) {
418  login_user(oUser);
419  send_response(281);
420  }
421  else {
422  oUser = 0;
423  send_response(482);
424  }
425  break;
426  default:
427  send_response(500);
428  }
429 }
430 
431 public:
432 
433 
434 /**
435  * Send back the body of article 'id'.
436  *
437  * @param string|void id - send body of id or current article.
438  */
439 protected:
440  void body(string|void id) {
441  object article;
442  CHECK_GROUP;
443 
444  if ( stringp(id) ) {
445  article = select_article(id);
446  }
447  else {
448  CHECK_ARTICLE;
449  article = oCurrentArticle;
450  }
451  send_result("223", (string)oCurrentGroup->get_article_num(article),
452  oCurrentGroup->message_id(article),
453  "article retrieved - body follows");
454  send_message(oCurrentGroup->get_body(article)+"\r\n");
455  send_message(".\r\n");
456 }
457 
458 public:
459 
460 /**
461  * This command returns a one-line response code of 111 followed by the
462  * GMT date and time on the server in the form YYYYMMDDhhmmss.
463  */
464 protected:
465  void date() {
466  Calendar.Calendar cal = Calendar.now()-> set_timezone(Calendar.Timezone["GMT"]);
467  string YYYYMMDDhhmmss = cal->format_ymd_short() + cal->format_tod_short();
468  send_result("111", YYYYMMDDhhmmss);
469 }
470 
471 public:
472 
473 /**
474  * Send back the head of article 'num' or the currently selected one.
475  *
476  * @param string|void num - number of article.
477  */
478 protected:
479  void head(string|void num)
480 {
481  object article;
482  CHECK_GROUP;
483 
484  if ( stringp(num) )
485  article = select_article(num);
486  else {
487  CHECK_ARTICLE;
488  article = oCurrentArticle;
489  }
490 
491  string header = oCurrentGroup->get_header(article);
492 
493  if ( !stringp(header) )
494  send_response(430);
495  else {
496  send_result("221", (string)oCurrentGroup->get_article_num(article),
497  oCurrentGroup->message_id(article),
498  "article retrieved - head follows");
499 
500  send_message(header);
501  send_message(".\r\n");
502  }
503 }
504 
505 public:
506 
507 /**
508  * Sets mode to posting.
509  *
510  */
511 protected:
512  void post()
513 {
514  if ( oUser == _GUEST ) {
515  send_response(480);
516  return;
517  }
518 
519  iMode = MODE_POST;
520  sPost = "";
521  send_response(340);
522 }
523 
524 public:
525 
526 
527 
528 /**
529  * Retrieve header and body of article 'num'.
530  *
531  * @param string num - the article to retrieve.
532  */
533 protected:
534  void article(string num)
535 {
536  object article;
537 
538  CHECK_GROUP;
539  article = select_article(num);
540 
541  send_result("220", (string)oCurrentGroup->get_article_num(article),
542  oCurrentGroup->message_id(article),
543  "article retrieved - head and body follows");
544  send_message(oCurrentGroup->get_header(article)+"\r\n");
545  send_message(oCurrentGroup->get_body(article)+"\r\n.\r\n");
546 }
547 
548 public:
549 
550 /**
551  * Process the command 'cmd'.
552  *
553  * @param string cmd - the command to process.
554  */
555 protected:
556  void process_command(string cmd)
557 {
558  array commands;
559 
560 
561  if ( iMode == MODE_POST ) {
562  if ( cmd == "." ) {
563  iMode = MODE_READ;
564  int result = oGroups->post_article(sPost);
565  if ( result == 1 )
566  send_response(240);
567  else if ( result == -1 )
568  send_response(440);
569  else
570  send_response(441);
571  }
572  else
573  sPost += cmd + "\r\n";
574  return;
575  }
576 
577  mixed err = catch {
578  commands = cmd / " ";
579  if ( !arrayp(commands) || sizeof(commands) == 0)
580  commands = ({ cmd });
581 
582  NNTP_LOG("commands:"+sprintf("%O\n", commands));
583  switch ( lower_case(commands[0]) ) {
584  case "quit":
585  if (objectp(oUser) )
586  oUser->disconnect();
587  close_connection();
588  break;
589  case "date":
590  date();
591  break;
592  case "list":
593  if ( sizeof(commands) == 2 )
594  list_groups(commands[1]);
595  else
596  list_groups();
597  break;
598  case "newgroups":
599  // TODO "GMT" could be third command - we ignore timezones at the moment
600  new_groups(commands[1], commands[2]);
601  break;
602  case "newsgroups":
603  // actually this command doesnt exist in RFC 977
604  list_new_groups((int)commands[1], (int)commands[2]);
605  break;
606  case "newnews":
607  // TODO "GMT" could be fourth command - we ignore timezones at the moment
608  // TODO distributions could be fourth or fith command - ignored
609  new_news(commands[1], commands[2], commands[3]);
610  break;
611  case "xover":
612  //RFC 2980
613  if ( sizeof(commands) == 2 )
614  xover(commands[1]);
615  else
616  xover();
617  break;
618  case "stat":
619  stat(commands[1]);
620  break;
621  case "mode":
622  mode(commands[1]);
623  break;
624  case "group":
625  group(commands[1]);
626  break;
627  case "head":
628  if ( sizeof(commands) == 1 )
629  head();
630  else
631  head(commands[1]);
632  break;
633  case "body":
634  if ( sizeof(commands) == 1 )
635  body();
636  else
637  body(commands[1]);
638  break;
639  case "next":
640  next();
641  break;
642  case "article":
643  article(commands[1]);
644  break;
645  case "post":
646  post();
647  break;
648  case "authinfo":
649  if ( sizeof(commands) != 3 )
650  send_response(501);
651  else
652  authorize(@commands[1..]);
653  break;
654  default:
655  send_response(500);
656  break;
657  }
658  };
659  if ( err != 0 ) {
660  if ( arrayp(err) && sizeof(err) == 3 && err[2] & E_ACCESS )
661  send_response(502);
662  else
663  send_response(503);
664  FATAL("Error!-------------\n"+err[0]+"\n"+sprintf("%O", err[1]));
665  }
666 }
667 
668 public:
669 
670 /**
671  * Send back a response with code 'code' and an optional id.
672  *
673  * @param int code - response code.
674  * @param int|void id - message id.
675  */
676 void send_response(int code, int|void id)
677 {
678  NNTP_LOG("RESPONSE: " + code + "("+mResponses[code]+")");
679  if ( id > 0 ) {
680  send_message(code + " " + id + " <"+id+"@"+_Server->get_server_name()
681  +" " + mResponses[code]+"\r\n");
682  }
683  else
684  send_message(code + " " + mResponses[code] + "\r\n");
685 }
686 
687 void send_result(mixed ... result)
688 {
689  ::send_result(@result);
690  NNTP_LOG("Result="+(result * " ")+"\r\n");
691 }
692 
693 /**
694  * Constructor of the nntp socket.
695  *
696  * @param object f - file descriptor to assign.
697  */
698 void create(object f)
699 {
700  ::create(f);
701  LOG("Setting Groups object...\n");
702  oGroups = _Server->get_module("nntp");
703  oUser = _GUEST;
704 
705  send_response(200);
706 }
707 
708 string get_socket_name() { return "nntp"; }
709 
710 
711 };