/* Do not remove the headers from this file! see /USAGE for more info. */ // An NNTP client object. (RFC 977: Network News Transfer Protocol) // Protocol by Brian Kantor and Phil Lapsley. // Created: May 4, 1995 by John Viega (rust@virginia.edu) // More user friendly date handling added Aug 8. by Edward Kmett (Harmless) // ARTICLE reponse code, well um.. removed by Edward Kmett #include <socket.h> #include <mudlib.h> #include <driver/localtime.h> #define NNTP_PORT "119" #define MAX_PORT 32000 //:MODULE //interface to an external nntp server. private object news_socket; private string news_host; private string news_port; private string* expected_responses = ({"WELCOME"}); private string* post_queue = ({}); private function response_callback; private string* long_response; private int long_response_code; private int can_post = 1; // assume you can until we get the WELCOME // screen. This SHOULD be known in advance... private int waiting_for_end_of_long_response = 0; private string* long_responses = ({ 100, 215, 220, 221, 222, 230, 231}); private string CRLF = "\r\n"; private nomask void handle_response(int code, mixed response) { string this_response = expected_responses[0]; int count, first, last; string name; int i; mixed id, nr, tmp; if(sizeof(expected_responses) > 1) expected_responses = expected_responses[1..<1]; else expected_responses = ({}); switch(this_response) { case "WELCOME": if(code == 200) can_post = 1; else can_post = 0; return; case "LIST": if(code != 215) { evaluate(response_callback, "nntp error: unexpected reply: " +code); return; } evaluate(response_callback, map_array(response[1..],(:explode($1," "):))); return; case "GROUP": if(code != 211) { evaluate(response_callback, "nntp error: unexpected reply: " + code); return; } response = explode(response, " "); i = sizeof(response); if(i>1) { count = response[1]; if(i>2) { first = response[2]; if(i>3) { last = response[3]; if(i>4) { name = response[4]; } } } } evaluate(response_callback, code, count, first, last, name); return; case "STAT_NEXT_OR_LAST": if((code-22)%100) { evaluate(response_callback, "nntp error: unexpected reply:" + code); return; } response = explode(response, " "); id = ""; i = sizeof(response); if(i > 1) { nr = response[1]; if(i > 2) id = response[2]; } evaluate(response_callback, code, nr, id); return; case "ARTICLE_OF_CLOTHING": if((code-20)%100) { evaluate(response_callback, "nntp error: unexpected reply: " + code); return; } tmp = explode(response, " "); id = ""; i = sizeof(tmp); if(i > 1) { nr = tmp[1]; if(i > 2) id = tmp[2]; } if(sizeof(response) > 1) response = response[1..]; else response = ({}); evaluate(response_callback, nr, id, response); return; case "XHDR": evaluate(response_callback, "nntp error: not implemented yet."); return; case "QUIT": evaluate(response_callback, response); news_socket->remove(); return; //Urg, don't send IHAVE and something else in the same execution thread. //I'll add a workaround in a day or 2. case "IHAVE": if(code < 300 || code > 399) { evaluate(response_callback, "nntp error: unexpected reply."); return; } expected_responses += ({"IHAVE_CONFIRMATION"}); tmp = post_queue[0]; if(sizeof(post_queue) > 1) post_queue = post_queue[1..]; else post_queue = ({}); for(i=0;i<sizeof(tmp);i++) { if(tmp[i] == ".") tmp[i] = ".."; news_socket->send(tmp[i] + CRLF); } news_socket->send("."+ CRLF); evaluate(response_callback, response); return; default: evaluate(response_callback, response); return; } } private nomask void nntp_read_callback(object socket, string data) { int num_lines, i, response_code; string *lines_read; string response; BBUG("Reading: " + data); lines_read = explode(data,"\n"); num_lines = sizeof(lines_read); for(i=0; i<num_lines ;i++) { if(waiting_for_end_of_long_response) { if(trim_spaces(lines_read[i]) == ".") { waiting_for_end_of_long_response = 0; handle_response(long_response_code, long_response); continue; } long_response += ({lines_read[i]}); continue; } if(sscanf(lines_read[i], "%d %s", response_code, response) != 2) { evaluate(response_callback, "nntp error: invalid response : "+ lines_read[i]+"."); continue; } if(member_array(response_code, long_responses) != -1) { waiting_for_end_of_long_response = 1; long_response_code = response_code; long_response = ({lines_read[i]}); continue; } handle_response(response_code, lines_read[i]); } } private nomask void nntp_close_callback(object socket) { } varargs void create(function callback, string host, mixed port) { if(!callback && !host && !port) return; if(!functionp(callback)) error("bad type arg 1 to create an NNTP object."); response_callback = callback; if(!host || !stringp(host)) error("bad type arg 2 to create NNTP."); if(!stringp(port)) { if(!port) { port = NNTP_PORT; } else { if(intp(port) && port > 0 && port <= MAX_PORT) { port = sprintf("%d", port); } else { error("Bad type argument 3 to create an NNTP object."); } } } news_host = host; news_port = port; news_socket = new(SOCKET, SKT_STYLE_CONNECT, host+" "+port, (: nntp_read_callback :), (: nntp_close_callback :)); } private mapping remap = ([ "Jan":"01","Feb":"02","Mar":"03", "Apr":"04","May":"05","Jun":"06", "Jul":"07","Aug":"08","Sep":"09", "Oct":"10","Nov":"11","Dec":"12" ]); //:FUNCTION dt_stamp //Return an nntp compatible yymmdd hhmmss string string dt_stamp(int time) { string s; int * tmp ; tmp = localtime(time); s = ctime(time+tmp[LT_GMTOFF]); if (s[8] == ' ') return s[22..] + remap[s[4..6]] + "0" + s[9..12] + s[14..15] + s[17..18]; else return s[22..] + remap[s[4..6]] + s[8..12] + s[14..15] + s[17..18]; } //:FUNCTION newgroups //Send an nntp NEWGROUPS command. void newgroups(int datetime) { expected_responses += ({"NEWGROUPS"}); news_socket->send("NEWGROUPS "+dt_stamp(datetime)+ " GMT"+CRLF); } //:FUNCTION newnews //Send an nntp NEWNEWS command. void newnews(string group, int datetime) { expected_responses += ({"NEWNEWS"}); news_socket->send("NEWNEWS " + group + " " + dt_stamp(datetime) + " GMT"+CRLF); } //:FUNCTION list //Send an nntp LIST command. void list() { expected_responses += ({"LIST"}); news_socket->send("LIST"+CRLF); } //:FUNCTION group //Send an nntp GROUP command. void group(string name) { expected_responses += ({"GROUP"}); news_socket->send("GROUP "+name+CRLF); } //:FUNCTION help //Send an nntp HELP command. void help() { expected_responses += ({"HELP"}); news_socket->send("HELP"+CRLF); } //:FUNCTION stat //Send an nntp STAT command. void stat(string id) { expected_responses += ({"STAT_NEXT_OR_LAST"}); news_socket->send("STAT " + id+CRLF); } //:FUNCTION next //Send an nntp NEXT command. void next() { expected_responses += ({"STAT_NEXT_OR_LAST"}); news_socket->send("NEXT"+CRLF); } //:FUNCTION last //Send an nntp LAST command. void last() { expected_responses += ({"STAT_NEXT_OR_LAST"}); news_socket->send("LAST"+CRLF); } //:FUNCTION head //Send an nntp HEAD command. varargs void head(string id) { expected_responses += ({"ARTICLE"}); if(!id) news_socket->send("HEAD"+CRLF); else news_socket->send("HEAD "+id+CRLF); } //:FUNCTION body //Send an nntp BODY command. varargs void body(string id) { expected_responses += ({"ARTICLE"}); if(!id) news_socket->send("BODY"+CRLF); else news_socket->send("BODY "+id+CRLF); } //:FUNCTION article //Send an nntp ARTICLE command. varargs void article(string id) { expected_responses += ({"ARTICLE"}); if(!id) news_socket->send("ARTICLE"+CRLF); else news_socket->send("ARTICLE "+id+CRLF); } //:FUNCTION xhdr //Send an nntp XHDR command. void xhdr(string hdr, string str) { expected_responses += ({"XHDR"}); news_socket->send("XHDR "+hdr+" "+str+CRLF); } //:FUNCTION post //Send an nntp POST command. void post(string file) { mixed contents; int i; if(can_post != 1) { evaluate(response_callback, "nntp error: not confirmed for posting."); return; } if(catch(contents = explode(read_file(file),"\n"))) { evaluate(response_callback, "nntp error: couldn't find local file."); return; } expected_responses += ({"POST"}); post_queue += ({file}); news_socket->send("POST"+CRLF); for(;i<sizeof(contents); i++) { if(contents[i] == ".") contents[i] = ".."; news_socket->send(contents[i]+CRLF); } news_socket->send("."+CRLF); } //:FUNCTION slave //Send an nntp SLAVE command. void slave() { expected_responses += ({"SLAVE"}); news_socket->send("SLAVE"+CRLF); } //:FUNCTION ihave //Send an nntp IHAVE command. void ihave(string id, mixed file) { if(can_post != 1) { evaluate(response_callback, "nntp error: not confirmed for posting."); return; } if(catch(file = explode(read_file(file),"\n"))) { evaluate(response_callback, "nntp error: couldn't find local file."); return; } expected_responses += ({"IHAVE"}); post_queue += ({file}); news_socket->send("IHAVE "+id+CRLF); } //:FUNCTION quit //Send an nntp QUIT command, and clean up. void quit() { expected_responses += ({"QUIT"}); news_socket->send("QUIT"+CRLF); } void remove() { catch(news_socket->remove()); }