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.1 2008/03/31 13:39:57 exodusd Exp $
18  */
19 inherit "/kernel/module";
20 #include <macros.h>
21 #include <database.h>
22 #include <attributes.h>
23 #include <classes.h>
24 #include <exception.h>
25 //! This is a wrapper class for use with nttp. It encapsulates sTeam
26 //! message boards (objects with annotations in general) and supports
27 //! nntp functionality for them.
28 class nntp : public module{
29 public:
30 
31 
32 
33 
34 
35 #define NNTP_DEBUG
36 
37 #ifdef NNTP_DEBUG
38 #define NNTP_LOG(s, args...) werror(s +"\n", args);
39 #else
40 #define NNTP_LOG(s, args...)
41 #endif
42 
43 #define HEADER_SEP "\r\n"
44 #define HEADER_SEP_RCV "\r\n\r\n"
45 
46 
47  array(Group) aGroups;
48  string sServer;
49 
50 class Group {
51 public:
52  object oSteamObj;
53  string name = 0;
54  // int iStart, iEnd;
55  mapping mReferences = ([ ]);
56  mapping mArticles = ([ ]); //maps the article -> article number
57 
58 
59  string get_name() {
60  if ( name != 0 )
61  return name;
62  if ( !objectp(oSteamObj) )
63  return "";
64 
65  if ( !objectp(_FILEPATH) )
66  return "steam." + oSteamObj->get_identifier();
67  string path = _FILEPATH->object_to_filename(oSteamObj);
68  array tokens = path / "/";
69  if ( sizeof(tokens) < 3 )
70  name = replace(path,"/",".");
71  else
72  name = (tokens[2..]*".");
73  name = replace(name, ({ " ", "\t" }), ({ "_", "_" }));
74  return name;
75  }
76 
77  /**
78  * In case of no articles in the group: return 0 ; see RFC 3977 6.1.1.2. the second case
79  */
80  int get_last_message() {
81  // return iEnd;
82  int max = 0;
83  foreach( values(mArticles), int number ){
84  if(number>max)
85  max=number;
86  }
87  return max;
88  }
89 
90  /**
91  * In case of no articles in the group: return 0 ; see RFC 3977 6.1.1.2. the second case
92  */
93  int get_first_message() {
94  // return iStart;
95  if(sizeof(mArticles)==0)
96  return 0;
97  int min = -1;
98  foreach( values(mArticles), int number ){
99  if( (number<min) || (min == -1) ){
100  min = number;
101  }
102  }
103  return min;
104  }
105 
106  /**
107  * In case of no articles in the group: return 0 ; see RFC 3977 6.1.1.2. the second case
108  * @return the total count of messages in the current group
109  */
110  int get_num_messages() {
111  array articles = get_articles();
112  return sizeof(articles);
113  }
114  bool read_access(object user) {
115  mixed err = catch {
116  _SECURITY->access_read(0, oSteamObj, user);
117  };
118  return err == 0;
119  }
120  bool write_access(object user) {
121  mixed err = catch {
122  _SECURITY->access_write(0, oSteamObj, user);
123  };
124  return err == 0;
125  }
126  bool newer_than(int date, int t) {
127  // TODO
128  return true;
129  }
130  void add_article(object article, string ref) {
131  object refobj;
132  if ( !stringp(ref) )
133  refobj = oSteamObj;
134  else {
135  int iRef;
136  array iRefs;
137  // we take the reference with the highest object-id (the newest post)
138  sscanf(ref, "%{%[ ]<%d%s>%}", iRefs);
139  if( sizeof(iRefs) > 0 ){
140  iRef = iRefs[0][1];
141  foreach( iRefs, array iRefsEntry ) {
142  if( iRefsEntry[1] > iRef )
143  iRef = iRefsEntry[1];
144  }
145  refobj = find_object(iRef);
146  }
147  }
148  refobj->add_annotation(article);
149  article->set_acquire(refobj);
150  }
151  bool can_post() {
152  mixed err = catch {
153  _SECURITY->access_annotate(0, oSteamObj, this_user(), 0);
154  };
155  if ( err != 0 ) {
156  FATAL("Not allowed to post: " + err[0]);
157  return false;
158  }
159 
160  return true;
161  }
162 
163 protected:
164  void get_sub_articles(object article) {
165  array subarticles = article->get_annotations_for();
166  foreach( subarticles, object sub) {
167  mReferences[sub] = article;
168  // mArticles[sub] = iEnd++;
169  mArticles[sub] = sub->get_object_id();
170  get_sub_articles(sub);
171  }
172 
173  }
174 
175 public:
176  // Fixme: should only fetch articles from time to time
177  array get_articles() {
178  // iStart = 1;
179  // iEnd = 0;
180 
181  array articles = oSteamObj->get_annotations_for();
182  NNTP_LOG("get_articles() == \n"+sprintf("%O", articles)+
183  "\n for "+oSteamObj->get_identifier());
184  foreach ( articles, object article ) {
185  // iEnd++;
186  // mArticles[article] = iEnd;
187  mArticles[article] = article->get_object_id();
188  get_sub_articles(article);
189  }
190  return indices(mArticles);
191  }
192  object get_article(int id) {
193  NNTP_LOG("get_article(%d)\n", id);
194  NNTP_LOG("Articles = %O\n", mArticles);
195  foreach(indices(mArticles), object article)
196  if ( mArticles[article] == id )
197  return article;
198  return 0;
199 
200  }
201  int get_article_num(object article) {
202  // return mArticles[article];
203  return article->get_object_id();
204  }
205 
206  string get_time(int t) {
207  string tf = ctime(t);
208  sscanf(tf, "%s\n", tf);
209  return tf;
210  }
211 
212  /**
213  * used in XOVER for a overview of an article
214  */
215  string header(object article) {
216  object creator = article->get_creator();
217  string name = creator->query_attribute(USER_EMAIL);
218  if ( !stringp(name) )
219  name = creator->get_identifier();
220  else
221  name = creator->query_attribute(USER_FULLNAME) + " <"+name+">";
222 
223  string res = mArticles[article] + "\t"+
224  article->query_attribute(OBJ_NAME)+"\t"+
225  name+"\t"+
226  get_time(article->query_attribute(OBJ_CREATION_TIME))+"\t"+
227  message_id(article)+"\t"+
228  (objectp(mReferences[article]) ? get_references(article)+"\t":"")+
229  article->get_content_size()+"\t"+
230  sizeof((article->get_content()/"\n"))+"\t"+
231  "Xref: "+_Server->get_server_name()+" " + get_name()+ ":"+mArticles[article];
232  NNTP_LOG("Header:\n"+res);
233  return res;
234  }
235  string header2(object article)
236  {
237  // taken from _Server->get_module("message")->header(object)
238  if ( !objectp(article) ) return "";
239  object creator = article->get_creator();
240  string name = creator->query_attribute(USER_EMAIL);
241  if ( !stringp(name) || name == "" )
242  name = creator->get_identfier() + "@"+sServer;
243  else
244  name = creator->query_attribute(USER_FULLNAME) + " <"+name+">";
245  return "From: " + name + HEADER_SEP +
246  "Date: " + timelib.smtp_time(article->query_attribute(OBJ_CREATION_TIME))+
247  HEADER_SEP+
248  "Subject: "+article->query_attribute(OBJ_NAME)+HEADER_SEP+
249  "Message-ID: " + message_id(article) +HEADER_SEP+
250  "Lines: " + (sizeof((article->get_content()/"\n"))) + HEADER_SEP;
251  }
252 
253  /**
254  * Formats the message-id (see RFC 1036, RFC 822)
255  */
256  string message_id(object article) {
257  object id;
258  if ( objectp(article) )
259  id = article->get_object_id();
260  else
261  id = article;
262  return "<" + id + "@" + sServer + ">";
263  }
264 
265 
266  string get_references(object article) {
267  if ( !objectp(mReferences[article]) )
268  return "";
269  string ref = get_references(mReferences[article]);
270  if ( strlen(ref) > 0 ) ref += " ";
271  ref += message_id(mReferences[article]);
272  return ref;
273  }
274 
275  string get_header(object article) {
276  if ( !objectp(article) )
277  return 0;
278 
279  // string header = _Server->get_module("message")->header(article);
280  string header = header2(article);
281 
282  header += "Path: not-for-mail"+HEADER_SEP+
283  "User-Agent: sTeam Forum"+HEADER_SEP+
284  "Newsgroups: " + get_name()+HEADER_SEP+
285  "Xref: "+_Server->get_server_name()+" "+get_name()+":"+mArticles[article]+HEADER_SEP+
286  (objectp(mReferences[article]) ?
287  "References:" + get_references(article) + HEADER_SEP : "");
288  return header;
289  }
290  string get_body(object article) {
291  return article->get_content();
292  }
293  object get_next_article(object article) {
294  array ids = values(mArticles);
295  sort(ids);
296  int nextInIds = ids[1 + search( ids, article->get_object_id() )];
297  return search(mArticles, nextInIds);
298  }
299 protected:
300  void create(object o, string|void grp_name) {
301  oSteamObj = o;
302  if ( stringp(grp_name) )
303  name = grp_name;
304  }
305 
306 public:
307  int get_object_id() {
308  return (objectp(oSteamObj) ?
309  oSteamObj->get_object_id() :
310  0);
311  }
312  object get_object() { return oSteamObj->get_object(); }
313  string get_identifier() { return "nntp:mailbox"; }
314 }
315 
316 /**
317  * Callback to initialize the module.
318  *
319  */
320 private:
321 void init_module()
322 {
323  object objects = _Server->get_module("objects");
324  if ( objectp(objects) )
325  aGroups = ({ Group(objects->lookup("bugs")),
326  Group(objects->lookup("ideas")) });
327  else
328  aGroups = ({ });
329  sServer = _Server->get_server_name();
330  set_attribute(OBJ_DESC, "This module functions as a nntp server.");
331  add_data_storage(STORE_NEWSGRP, retrieve_groups, restore_groups);
332 }
333 
334 public:
335 
336 /**
337  * Restore function to restore group data of NNTP.
338  *
339  * @param mapping data - the saved data mapping.
340  */
341 private:
342 final void restore_groups(mapping data)
343 {
344  if ( CALLER != _Database )
345  THROW("Invalid call to restore_groups() !", E_ACCESS);
346  array groups = data["groups"];
347  aGroups = ({ });
348  foreach(groups, object g) {
349  if ( objectp(g) )
350  aGroups += ({ Group(g) });
351  }
352 }
353 
354 public:
355 
356 /**
357  * Retrieve the group data.
358  *
359  * @return Mapping of group data to be saved.
360 private:
361  * @see restore_groups
362  */
363 private:
364 final mapping retrieve_groups()
365 {
366  if ( CALLER != _Database )
367  THROW("Invalid call to retrieve_groups()", E_ACCESS);
368  array groups= ({ });
369  foreach(aGroups, object g)
370  groups += ({ g->oSteamObj });
371 
372  return ([ "groups": groups, ]);
373 }
374 
375 public:
376 
377 /**
378  * Register a new group.
379  *
380  * @param object grp - the new group to register.
381  * @param string|void name - optional name for the group.
382  * @return newly created Group class for the given group.
383  */
384 object register_group(object grp, string|void name)
385 {
386  if ( !_Server->is_a_factory(CALLER) )
387  return 0;
388  object group = Group(grp, name);
389  aGroups += ({ group });
390  require_save(STORE_NEWSGRP);
391  return group;
392 }
393 
394 /**
395  * List the registered groups.
396  *
397  * @return array of registered Groups.
398  */
399 array(Group) list_groups()
400 {
401  return aGroups;
402 }
403 
404 /**
405  * Get a certain group identified by 'id'.
406  *
407  * @param string id - the id of the group to get.
408  * @return the Group or 0.
409  */
410 Group get_group(string id)
411 {
412  NNTP_LOG("get_group("+id+")");
413  foreach(aGroups, object grp) {
414  if ( grp->get_name() == id )
415  return grp;
416  }
417  return 0;
418 }
419 
420 /**
421  * Get a mapping of article headers for an article with 'content'.
422  *
423  * @param string content - the content of the NNTP article.
424  * @return mapping of headers.
425  */
426 mapping article_header(string content)
427 {
428  string header;
429  mapping header_map;
430 
431  // dont know whats this ????!!!! - cutting the header of at HEADER_SEP_RCV
432  int i = search(content, HEADER_SEP_RCV);
433  header = content[..i-1];
434  array settings = header / (HEADER_SEP);
435  header_map = ([ ]);
436  foreach(settings, string setting ) {
437  string key, val;
438  if ( sscanf(setting, "%s: %s", key, val) == 2 )
439  header_map[lower_case(key)] = val;
440  }
441  NNTP_LOG("Header-Map="+sprintf("%O",header_map));
442  return header_map;
443 }
444 
445 /**
446  * Find all group in a string 'id' separated by ','. This subsequently calls
447  * get_group for each group found in 'id'.
448  *
449  * @param string id - groups id string.
450  * @return array of found groups.
451  * @see get_group
452  */
453 array find_groups(string id)
454 {
455  array grps = id / ",";
456  array groups = ({ });
457  if ( !arrayp(grps) )
458  grps = ({ id });
459 
460  NNTP_LOG("find_groups("+id+")");
461 
462  foreach(grps, string grp) {
463  groups += ({ get_group(grp) });
464  }
465  return groups;
466 }
467 
468 /**
469  * Get the body text of an article which means the
470  * separator is found and the rest of the articles content returned.
471  *
472  * @param string content - the content of the article.
473  * @return the body.
474  * @see article_header
475  */
476 string get_body(string content)
477 {
478  int i = search(content, HEADER_SEP_RCV);
479  string body = content[i+2..];
480  return body;
481 }
482 
483 /**
484  * Post an article with content. This will parse the header information
485  * and create an appropriate object inside sTeam.
486  *
487  * @param string content - the posted article.
488  * @return -1 no posting allowed, 0 posting failed, 1 success
489  */
490 int post_article(string content)
491 {
492  NNTP_LOG("New article :\n"+content);
493  mapping header = article_header(content);
494  object factory = _Server->get_factory(CLASS_DOCUMENT);
495  object group;
496 
497  bool post = false;
498  array groups = find_groups(header["newsgroups"]);
499  foreach ( groups, group ) {
500  NNTP_LOG("Checking group: " + group->get_name() + " for posting...");
501  if ( group->can_post() ) {
502  NNTP_LOG("Posting allowed...");
503  post = true;
504  }
505  }
506  if ( post == false )
507  return -1;
508 
509 
510 
511  string mimetype = header["content-type"];
512  if ( !stringp(mimetype) )
513  mimetype = "text/html";
514 
515  sscanf(mimetype, "%s;%*s", mimetype);
516  object article = factory->execute( ([ "name": header["subject"],
517  "mimetype": mimetype, ]));
518  article->set_content(get_body(content));
519  mixed err = catch {
520  foreach(groups, group) {
521  LOG("Adding annotation on group...\n");
522  }
523  };
524  if ( err != 0 ) {
525  if ( arrayp(err) && sizeof(err) == 3 && err[2] == E_ACCESS )
526  return -1;
527  else
528  throw(err);
529  return 0;
530  }
531  return 1;
532 }
533 
534 
535 string get_identifier() { return "nntp"; }
536 
537 
538 
539 };