From 6cfaa6fdd04e04b626a4880a9eb2af0cfe8bb2d8 Mon Sep 17 00:00:00 2001 From: "Mirar (Pontus Hagland)" <pike@sort.mirar.org> Date: Wed, 27 Oct 1999 20:01:16 +0200 Subject: [PATCH] Protocols.IRC :) Rev: lib/modules/Protocols.pmod/IRC.pmod/Client.pike:1.1 Rev: lib/modules/Protocols.pmod/IRC.pmod/Error.pmod:1.1 Rev: lib/modules/Protocols.pmod/IRC.pmod/Raw.pike:1.1 Rev: lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod:1.1 --- .gitattributes | 1 + .../Protocols.pmod/IRC.pmod/Client.pike | 393 ++++++++++++++++++ .../Protocols.pmod/IRC.pmod/Error.pmod | 47 +++ lib/modules/Protocols.pmod/IRC.pmod/Raw.pike | 146 +++++++ .../Protocols.pmod/IRC.pmod/Requests.pmod | 114 +++++ 5 files changed, 701 insertions(+) create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Client.pike create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Error.pmod create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Raw.pike create mode 100644 lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod diff --git a/.gitattributes b/.gitattributes index 9b807419db..82a1b7eb0e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -54,6 +54,7 @@ testfont binary /lib/modules/MIME.pmod foreign_ident /lib/modules/Protocols.pmod/IMAP.pmod/imap_server.pike foreign_ident /lib/modules/Protocols.pmod/IMAP.pmod/requests.pmod foreign_ident +/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod foreign_ident /lib/modules/Protocols.pmod/Ident.pmod foreign_ident /lib/modules/Protocols.pmod/LDAP.pmod/client.pike foreign_ident /lib/modules/Protocols.pmod/LDAP.pmod/ldap_errors.h foreign_ident diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Client.pike b/lib/modules/Protocols.pmod/IRC.pmod/Client.pike new file mode 100644 index 0000000000..00f2c6233b --- /dev/null +++ b/lib/modules/Protocols.pmod/IRC.pmod/Client.pike @@ -0,0 +1,393 @@ +import "."; + +object raw; +string pass=MIME.encode_base64(Crypto.randomness.reasonably_random()->read(6)); + +mapping options; + +mapping channels=([]); + +void create(string _server,void|mapping _options) +{ + options= + ([ + "port":6667, +#if constant(getpwuid) && constant(getuid) + "user":(getpwuid(getuid())||({"-"}))[0], +#else + "user":"unknown", +#endif +#if constant(getpwuid) && constant(getuid) + "nick":String.capitalize((getpwuid(getuid())||({"-"}))[0]), +#else + "nick":"Unknown"+random(10000), +#endif +#if constant(getpwuid) && constant(getuid) + "realname":(getpwuid(getuid())||({0,0,0,0,"Mr. Nobody"}))[4], +#else + "realname":"Mr. Anonymous", +#endif +#if constant(uname) + "host":uname()->nodename||"localhost", +#else + "host":"localhost", +#endif + ])|(_options||([])); + + options->server=_server; + + me=person(options->nick,options->user+"@"+options->host); + + raw=Raw(options->server,options->port,got_command,got_notify, + connection_lost); + + cmd->create(raw); + + cmd->pass(pass); + cmd->nick(options->nick); + cmd->user(options->user,options->host,options->server,options->realname); + + call_out(da_ping,30); +} + +string expecting_pong; + +void da_ping() +{ + call_out(da_ping,30); + call_out(no_ping_reply,30); // timeout + cmd->ping(expecting_pong= + options->host+" "+Array.shuffle("pike""IRC""client"/"")*""); +} + +void no_ping_reply() +{ + remove_call_out(da_ping); + werror("no ping reply\n"); + connection_lost(); +} + +void connection_lost() +{ + if (options->connection_lost) + options->connection_lost(); + else + { + werror("destructing self\n"); + catch { destruct(raw->con); }; + destruct(raw); + destruct(this_object()); + } +} + +void got_command(string what,string ... args) +{ + // werror("got command: %O, %O\n",what,args); + // most commands can be handled immediately + switch (what) + { + case "PING": + cmd->pong(args[0]); + return; + case "KILL": + case "ERROR": + if (options->error_notify) + options->error_notify(@args); + return; + } + werror("got command: %O, %O\n",what,args); +} + +void got_notify(string from,string type, + void|string to,void|string message, + void|string extra,void|string extra2) +{ + object c; + if (options->system_notify && glob("2??",type)) + { + options->system_notify(type,message); + return; + } + if (options->motd_notify && (<"372","375","376">)[type]) + { + options->motd_notify((["372":"cont", + "375":"start", + "376":"end"])[type],message); + return; + } + + object originator=person(@(from/"!")); + + switch (type) + { + case "433": // nick in use + if (options->error_nickinuse) + options->error_nickinuse(message); + else if (options->generic_notify) break; + cmd->nick("unknown"+random(10000)); + return; + + case "473": // failed to join + if ((c=channels[lower_case(message||"")])) + { + c->not_failed_to_join(); + return; + } + break; + + case "353": // names list + if (extra && (c=channels[lower_case(extra||"")])) + { + c->not_names(map(extra2/" "-({""}), + lambda(string name) + { + string a,b,c; + sscanf(name,"%[@]%s%[+]", + a,b,c); + return ({person(b),a+c}); + })); + return; + } + break; + + /* --- */ + + case "PONG": + if (message==expecting_pong) + { + remove_call_out(no_ping_reply); + return; + } + break; + + case "MODE": + if ((c=channels[lower_case(to||"")])) + { + // who, mode, by + c->not_mode(extra?person(extra):originator,message,originator); + return; + } + break; + + case "JOIN": + if ((c=channels[lower_case(to||"")])) + { + c->not_join(originator); + return; + } + break; + + case "PART": + if ((c=channels[lower_case(to||"")])) + { + // who, why, by + c->not_part(originator,message,originator); + return; + } + break; + + case "KICK": + if ((c=channels[lower_case(to||"")])) + { + // who, why, by + c->not_part(person(message),extra,originator); + return; + } + break; + + case "QUIT": + forget_person(originator); + foreach (values(channels),c) + c->not_part(originator,message,originator); + if (options->quit_notify) + { + // who, why + options->quit_notify(originator,to); + return; + } + break; + + case "PRIVMSG": + if ((c=channels[lower_case(to||"")])) + { + c->not_message(originator,message); + return; + } + if (!options->privmsg_notify) break; + options->privmsg_notify(originator,message,to); + return; + + case "NICK": +// werror("%s is known as %s (aka %s)\n", +// originator->nick, +// to, +// ((array)originator->aka)*","); + mixed err=0; + originator->aka[originator->nick]=1; + originator->aka[to]=1; + if (options->nick_notify) + err=catch { options->nick_notify(originator,to); }; + originator->nick=to; + nick2person[to]=originator; + if (err) throw(err); + return; + } +// werror("got notify: %O, %O, %O, %O\n",from,type,to,message); + if (options->generic_notify) + options->generic_notify(from,type,to,message,extra); +} + +object cmd=class +{ + object raw; + public void create(object _raw) { raw=_raw; } + + class SyncRequest + { + program prog; + object ret; + + void create(program p,object _ret) + { + prog=p; + ret=_ret; + } + + mixed `()(mixed ...args) + { + mixed m; + object req=prog(); + m=req->sync(raw,@args); + if (!m) return ret; + else return m; + } + } + + class AsyncRequest + { + program prog; + + void create(program p) + { + prog=p; + } + + mixed `()(mixed ...args) + { + object req=prog(); + req->async(this_object(),@args); + return req; + } + } + + class AsyncCBRequest + { + program prog; + + void create(program p) + { + prog=p; + } + + mixed `()(function callback,mixed ...args) + { + object req=prog(); + req->callback=callback; + req->async(this_object(),@args); + return req; + } + } + + mixed `->(string request) + { + mixed|program p; + + + if ( (p=Requests[request]) ) + return SyncRequest(p,this_object()); + else if ( request[..5]=="async_" && + (p=Requests[request[6..]]) ) + return AsyncRequest(p); + else if ( request[..8]=="async_cb_" && + (p=Requests[request[9..]]) ) + return AsyncCBRequest(p); + else + { + p=::`[](request); + if (!p) + { + werror("FOO! FOO! FOO! %O %O\n",request,p); + Error.internal("unknown command %O",request); + return 0; + } + return p; + } + } +} (raw); + +// ----- commands + +void send_message(string|array to,string msg) +{ + cmd->privmsg( (arrayp(to)?to*",":to), msg); +} + +// ----- channel + +class Channel +{ + string name; + + void not_message(string who,string message); + void not_join(string who); + void not_part(string who,string message,string executor); + void not_mode(string who,string mode); + void not_failed_to_join(); +} + +// ----- persons + +class Person +{ + string nick; // Mirar + string user; // mirar + string ip; // mistel.idonex.se + int last_action; // time_t + multiset aka=(<>); + + void say(string message) + { + cmd->privmsg(nick,message); + } + + void me(string what) + { + say("\1ACTION "+what+"\1"); + } +} + +mapping nick2person=([]); +object me; + +Person person(string who,void|string ip) +{ + Person p; + + if ( ! (p=nick2person[who]) ) + { + p=Person(); + p->nick=who; + nick2person[who]=p; + } + if (ip && !p->ip) + sscanf(ip,"%*[~]%s@%s",p->user,p->ip); + +// werror("%O is %O %O %O\n",who,p->nick,p->user,p->ip); + + return p; +} + +void forget_person(object p) +{ + m_delete(nick2person,p->nick); +} diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Error.pmod b/lib/modules/Protocols.pmod/IRC.pmod/Error.pmod new file mode 100644 index 0000000000..c0e6a4a1b2 --- /dev/null +++ b/lib/modules/Protocols.pmod/IRC.pmod/Error.pmod @@ -0,0 +1,47 @@ + +class StdErr +{ + constant is_generic_error=1; + constant is_irc_error=1; + + string name; + array(mixed) __backtrace; + + void create(string _name) + { + name=_name; + __backtrace=backtrace(); + while (!__backtrace[-1][0]|| + __backtrace[-1][0][strlen(__backtrace[-1][0])-11..] + == "/Error.pmod") + __backtrace=__backtrace[0..sizeof(__backtrace)-2]; + } + + mixed `[](mixed z) + { + switch (z) + { + case 0: return "Protocols.IRC: "+name+"\n"; + case 1: return __backtrace; + } + return ::`[](z); + } +} + +class Connection +{ + inherit StdErr; + + constant is_irc_connection_error=1; + + int errno; + + void create(string desc,int _errno) + { + ::create(desc+" ("+strerror(errno=_errno)+")"); + } +} + +void connection(mixed ...args) { throw(Connection(@args)); } +void internal(string format,mixed ...args) + { throw(StdErr(sprintf(format+" (internal error)",@args))); } diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Raw.pike b/lib/modules/Protocols.pmod/IRC.pmod/Raw.pike new file mode 100644 index 0000000000..9ce8706014 --- /dev/null +++ b/lib/modules/Protocols.pmod/IRC.pmod/Raw.pike @@ -0,0 +1,146 @@ +import "."; + +object con; + +// #define IRC_DEBUG + +function(string,string ...:void) command_callback; +function(string,string,...:void) notify_callback; +function(:void) close_callback; + +// command_callback(string cmd,string ... parameters) +// notify_callback(string from,string type,string from,string message) + +void create(string server,int port, + function(string,string ...:void) _command_callback, + void|function(string,string...:void) _notify_callback, + void|function(:void) _close_callback) +{ + array aserver=gethostbyname(server); + if (!aserver || !sizeof(aserver[1])) + Error.connection("Failed to lookup host %O",server); + server=aserver[1][random(sizeof(aserver[1]))]; + + con=Stdio.File(); + if (!con->connect(server,port)) + Error.connection("Failed to connect",con->errno()); + + con->set_nonblocking(con_read,0,con_close); + + command_callback=_command_callback; + notify_callback=_notify_callback; + close_callback=_close_callback; +} + +void con_close() +{ +#ifdef IRC_DEBUG + werror("-> (connection closed)\n"); +#endif + con->close(); + con=0; + if (close_callback) close_callback(); +} + +string buf=""; + +void con_read(mixed dummy,string what) +{ +#ifdef IRC_DEBUG + werror("-> %O\n",what); +#endif + buf+=what; + array lines=(buf-"\r")/"\n"; + buf=lines[-1]; + foreach (lines[0..sizeof(lines)-2],string row) + { + mixed err=catch + { + handle_command(row); + }; + if (err) + werror(master()->describe_backtrace(err)); + } +} + +array write_buf=({}); + +void con_write_callback() +{ + while (sizeof(write_buf)) + { + int j=con->write(write_buf[0]); + if (j!=strlen(write_buf[0])) + { + if (j==-1) + { +#ifdef IRC_DEBUG + werror("<- (write error)\n"); +#endif + break; // connection error? + } +#ifdef IRC_DEBUG + werror("<- %O (buffer full)\n",write_buf[0][0..j-1]); +#endif + write_buf[0]=write_buf[0][j..]; + } +#ifdef IRC_DEBUG + werror("<- %O (buffer finished)\n",write_buf[0]); +#endif + write_buf=write_buf[1..]; + } + con->set_nonblocking(con_read,0,con_close); +} + +void con_write(string s) +{ + if (sizeof(write_buf)) + { + write_buf+=({s}); + return; + } + int j=con->write(s); + if (j!=sizeof(s)) + { + if (j==-1) + { +#ifdef IRC_DEBUG + werror("<- (write error; %O)\n",strerror(con->errno())); +#endif + return; // connection broken? + } +#ifdef IRC_DEBUG + werror("<- %O [%d] (buffer full)\n",s[0..j-1],j); +#endif + write_buf=({s[j..]}); + con->set_nonblocking(con_read,con_write_callback,con_close); + } +#ifdef IRC_DEBUG + else + werror("<- %O [%d]\n",s,j); +#endif +} + +void transmit_noreply(string cmd,string args) +{ + con_write(cmd+" "+args+"\r\n"); +} + +void handle_command(string cmd) +{ + string a,b; + if (cmd=="") return; + if (cmd[0]==':') + { + if (!notify_callback) return; + if (sscanf(cmd,":%s :%s",a,b)==2) + notify_callback(@(a/" "),b); + else + notify_callback(@(cmd[1..]/" ")); + return; + } + if (sscanf(cmd,"%s :%s",a,b)==2) + command_callback(@(a/" "),b); + else + command_callback(@(cmd/" ")); +} diff --git a/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod b/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod new file mode 100644 index 0000000000..46aae2e20d --- /dev/null +++ b/lib/modules/Protocols.pmod/IRC.pmod/Requests.pmod @@ -0,0 +1,114 @@ + +/* +PASS gurka +NICK Mirar^ +USER mirar mistel.idonex.se irc.starchat.net :Mirar is testing +*/ + +string __cvs_id="$Id: Requests.pmod,v 1.1 1999/10/27 18:01:16 mirar Exp $"; + +import "."; + +class Request +{ + function callback; + string cmd=0; + + void async(object con,mixed ...args) + { + con->transmit_async(cmd,encode(args),got_answer); + } + + mixed sync(object con,mixed ...args) + { + return decode_answer(con->transmit(cmd,encode(args))); + } + + void got_answer(mixed s) + { + if (callback) callback(decode_answer(s)); + } + + void got_error(object error) + { + if (callback) callback(error); + } + + string encode(mixed ...); + mixed decode_answer(string s); +} + +class NoReply +{ + string source= +"#"+__LINE__+" \""+__FILE__+#" (NoReply.%cmd%)\" +inherit Protocols.IRC.Requests.Request; + +string cmd=\"%cmd%\"; + +void async(object con,mixed ...args) +{ + con->transmit_noreply(cmd,encode(args)); + if (callback) call_out(callback,0,1); +} + +int(1..1) sync(object con,mixed ...args) +{ + con->transmit_noreply(cmd,encode(args)); + return 1; +} + +string encode(array args) +{ + %encode% +} + +mixed decode_answer(string s) +{ + return 1; +} +"; + + string cmd; + program p; + + void create(string _cmd,string ...args) + { + source=replace(source,"%cmd%",cmd=_cmd); + array format=({}); + int i=0; + foreach (args,string type) + { + switch (type) + { + case "string": format+=({"%s"}); break; + case "text": format+=({":%s"}); break; + default: Error.internal("didn't expect type %O",type); + } + } + source=replace(source,"%encode%", + "return sprintf(\"" + +format*" "+"\",@args);"); + } + + object `()(mixed ... args) + { + if (!p) + { + p=compile_string(source,"NoReply."+cmd); + source=0; + } + return p(@args); + } +} + + +object pass=NoReply("PASS","string"); +object nick=NoReply("NICK","string"); +object user=NoReply("USER","string","string","string","text"); +object pong=NoReply("PONG","text"); +object ping=NoReply("PING","text"); +object privmsg=NoReply("PRIVMSG","string","text"); +object join=NoReply("JOIN","string"); +object names=NoReply("NAMES","string"); + -- GitLab