1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
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.
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.
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
17 * $Id: nntp.pike,v 1.2 2009/08/07 15:22:36 nicke Exp $
19 inherit "/net/coal/login";
20 inherit "/net/base/line";
24 class nntp : public login,line{
34 #define NNTP_LOG(s, args...) werror(s +"\n", args);
36 #define NNTP_LOG(s, args...)
39 #define CHECK_GROUP if ( !objectp(oCurrentGroup) ) {\
44 #define CHECK_ARTICLE if ( !objectp(oCurrentArticle) ) {\
53 int iMode = MODE_READ;
56 mapping mResponses = ([
57 100: "help text follows",
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",
92 503: "program fault - command not performed",
96 object oCurrentGroup = 0;
97 object oCurrentArticle = 0;
100 * Send a list of groups.
102 * @param void|string type - send subscribed groups or overview.fmt
105 void list_groups(void|string type)
107 if ( zero_type(type) || lower_case(type) == "subscriptions" ) {
109 array groups = oGroups->list_groups();
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"));
120 send_message(".\r\n");
123 if ( lower_case(type) == "overview.fmt" )
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");
143 * Send a list of new groups.
145 * @param string date - last newsgroup check date.
146 * @param string t - time of last check.
150 void new_groups(string date, string t)
152 send_result("231", "New newsgroups follow");
153 array groups = oGroups->list_groups();
155 if ( arrayp(groups) ) {
156 foreach(groups, object grp) {
157 // FIXME! Check if newer
158 send_result(grp->get_name());
167 * Get a list of new groups newer than date, time t.
169 * @param int date - date of last check
170 * @param int t - time of last check.
173 void list_new_groups(int date, int t)
176 array groups = oGroups->list_groups();
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"));
185 send_message(".\r\n");
191 * Get a list of new news entries newer than date, time t.
193 * @param int date - date of last check
194 * @param int t - time of last check.
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
203 // Todo ',' separated groupsnames
204 // Check 512 character command length limit
208 if( stringp(date) ) {
209 // Format is either YYMMDD with YY rounded to the nearest century (RFC 977)
211 if( sizeof(date)==8 ){
212 sscanf(date, "%4d%2d%2d", int year,int month,int day);
213 Calendar.Day(year,month,day);
215 NNTP_LOG("Got unsupported/bad date format '" + date + "'");
230 * Select an article from the current group.
232 * @param string num - number of the article.
233 * @return object of selected article.
236 object select_article(string num)
240 if ( sscanf(num, "<%d@%*s", art_num) == 0 ) {
242 // RFC says article pointer is set by article (numeric)
243 oCurrentArticle = oCurrentGroup->get_article(art_num);
244 return oCurrentArticle;
246 return find_object(art_num);
253 * Select the article with id 'id' and send status of it.
255 * @param string id - id of article
260 if ( !objectp(oCurrentGroup) ) {
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");
273 * Set the next article as current.
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");
293 * XOVER command sends article overview.
295 * @param string|void range
298 void xover(string|void range)
300 if ( !objectp(oCurrentGroup) ) {
304 NNTP_LOG("xover range=%O\n", range);
307 if ( stringp(range) ) {
308 if ( sscanf(range, "%d-%d", from, to) != 2 ) {
309 if ( sscanf(range, "%d-", from) == 1 ) {
312 else if ( sscanf(range, "%d", from ) == 1 ) {
321 if ( oCurrentArticle )
322 from = oCurrentGroup->get_article_num(oCurrentArticle);
324 from = oCurrentGroup->get_first_message();
325 to = from; // no argument given - use current message
327 array articles = oCurrentGroup->get_articles();
328 array inRange = ({ });
331 foreach( articles, art ) {
332 int oid = oCurrentGroup->get_article_num(art);
333 if ( oid >= from && oid <= to )
334 inRange += ({ art });
336 if ( sizeof(inRange) > 0 ) {
338 foreach(inRange, art) {
339 send_message(oCurrentGroup->header(art) + "\r\n");
346 send_message(".\r\n");
352 * Select the group 'grp' as current group.
354 * @param string grp - name of the group to select.
357 void group(string grp)
359 object group = oGroups->get_group(grp);
360 if ( !objectp(group) ) {
364 else if ( !group->read_access(oUser) ) {
365 if ( oUser == _GUEST )
368 send_response(411); // is this correct ? no such newsgroup ???
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(),
377 "Newsgroup selected");
383 * Set the mode to new mode 'm'. Just sends back 200 response.
385 * @param string m - the new mode.
390 send_result("200","Hello, you can post");
396 * Authorize with user or password.
398 * @param string subcmd - authorization method, only 'user' and 'pass' allowed.
399 * @param string auth - auth parameter.
402 void authorize(string subcmd, string auth)
404 LOG("authorize("+subcmd+","+auth+")");
405 switch ( lower_case(subcmd) ) {
407 oUser = _Persistence->lookup_user(auth);
408 LOG("User="+sprintf("%O", oUser));
409 if ( objectp(oUser) )
415 if ( !objectp(oUser) )
417 if ( oUser->check_user_password(auth) ) {
435 * Send back the body of article 'id'.
437 * @param string|void id - send body of id or current article.
440 void body(string|void id) {
445 article = select_article(id);
449 article = oCurrentArticle;
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");
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.
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);
474 * Send back the head of article 'num' or the currently selected one.
476 * @param string|void num - number of article.
479 void head(string|void num)
485 article = select_article(num);
488 article = oCurrentArticle;
491 string header = oCurrentGroup->get_header(article);
493 if ( !stringp(header) )
496 send_result("221", (string)oCurrentGroup->get_article_num(article),
497 oCurrentGroup->message_id(article),
498 "article retrieved - head follows");
500 send_message(header);
501 send_message(".\r\n");
508 * Sets mode to posting.
514 if ( oUser == _GUEST ) {
529 * Retrieve header and body of article 'num'.
531 * @param string num - the article to retrieve.
534 void article(string num)
539 article = select_article(num);
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");
551 * Process the command 'cmd'.
553 * @param string cmd - the command to process.
556 void process_command(string cmd)
561 if ( iMode == MODE_POST ) {
564 int result = oGroups->post_article(sPost);
567 else if ( result == -1 )
573 sPost += cmd + "\r\n";
578 commands = cmd / " ";
579 if ( !arrayp(commands) || sizeof(commands) == 0)
580 commands = ({ cmd });
582 NNTP_LOG("commands:"+sprintf("%O\n", commands));
583 switch ( lower_case(commands[0]) ) {
593 if ( sizeof(commands) == 2 )
594 list_groups(commands[1]);
599 // TODO "GMT" could be third command - we ignore timezones at the moment
600 new_groups(commands[1], commands[2]);
603 // actually this command doesnt exist in RFC 977
604 list_new_groups((int)commands[1], (int)commands[2]);
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]);
613 if ( sizeof(commands) == 2 )
628 if ( sizeof(commands) == 1 )
634 if ( sizeof(commands) == 1 )
643 article(commands[1]);
649 if ( sizeof(commands) != 3 )
652 authorize(@commands[1..]);
660 if ( arrayp(err) && sizeof(err) == 3 && err[2] & E_ACCESS )
664 FATAL("Error!-------------\n"+err[0]+"\n"+sprintf("%O", err[1]));
671 * Send back a response with code 'code' and an optional id.
673 * @param int code - response code.
674 * @param int|void id - message id.
676 void send_response(int code, int|void id)
678 NNTP_LOG("RESPONSE: " + code + "("+mResponses[code]+")");
680 send_message(code + " " + id + " <"+id+"@"+_Server->get_server_name()
681 +" " + mResponses[code]+"\r\n");
684 send_message(code + " " + mResponses[code] + "\r\n");
687 void send_result(mixed ... result)
689 ::send_result(@result);
690 NNTP_LOG("Result="+(result * " ")+"\r\n");
694 * Constructor of the nntp socket.
696 * @param object f - file descriptor to assign.
698 void create(object f)
701 LOG("Setting Groups object...\n");
702 oGroups = _Server->get_module("nntp");
708 string get_socket_name() { return "nntp"; }