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: jabber.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
19 inherit "/net/coal/login";
20 inherit "/kernel/coalsocket";
21 inherit "/net/base/cmd";
22 inherit Events.Listener;
25 #include <attributes.h>
29 class jabber : public login,coalsocket,cmd{
36 //#define JABBER_DEBUG
39 #define DEBUG_JABBER(s, args...) werror("Jab: "+s+"\n", args)
41 #define DEBUG_JABBER(s, args...)
44 class JabberListener {
47 void create(int events, object obj) {
48 ::create(events, PHASE_NOTIFY, obj, 0, oUser);
49 obj->listen_event(this_object());
52 void notify(int event, mixed args) {
53 notify_jabber(event, @args);
55 function get_callback() {
59 mapping save() { return 0; }
62 return "JabberListener()";
68 mapping command = ([ ]);
70 mapping mRegisterKeys = ([ ]);
73 JabberListener tellListen;
74 mapping loginListen = ([ ]);
75 mapping logoutListen = ([ ]);
76 mapping sayListen = ([ ]);
78 string sUserName = "";
80 #if constant(Parser.get_xml_parser)
81 Parser.HTML xmlParser = Parser.get_xml_parser();
88 send_message("<stream:stream from=\""+_Server->get_server_name()+
89 "\" xmlns=\"jabber:client\" "+
90 "xmlns:stream=\"http://etherx.jabber.org/streams\">");
95 send_message("</stream:stream>\n");
98 foreach(values(logoutListen), object o)
100 foreach(values(loginListen), object x)
102 foreach(values(sayListen), object s)
104 destruct(tellListen);
110 DEBUG_JABBER("Disconnecting");
111 if ( objectp(oUser) )
113 ::close_connection();
116 void send_iq_result(int code, string|void desc)
118 if ( !stringp(desc) ) desc = "";
120 if ( !stringp(command->iq->id) )
124 send_message("<iq type=\"result\" id=\""+command["iq"]["id"]+"\">\n"+
127 send_message("<iq type=\"result\" id=\""+command["iq"]["id"]+"\">\n"+
128 "<error code=\""+code+"\">"+desc+"</error>\n</iq>\n");
131 string name_on_server(string|object user)
137 else if ( objectp(user) )
138 n = user->get_identifier();
142 return n + "@"+_Server->get_server_name();
145 string get_nick(string name)
147 sscanf(name, "%s@%*s", name);
151 void notify_jabber(int event, mixed ... args)
153 DEBUG_JABBER("notify("+event+", in %O): " + sprintf("%O\n", args), oUser);
156 send_message("<presence from=\""+name_on_server(geteuid() || this_user()) +"\"/>");
159 send_message("<presence type=\"unavailable\" from=\""+
160 name_on_server(args[0]) +"\"/>");
163 string msg = htmllib.quote_xml( args[2] );
164 send_message("<message type='chat' to=\""+name_on_server(oUser)+
166 name_on_server(geteuid() || this_user())+
167 "\"><body>"+msg+"</body></message>\n");
170 if ( (geteuid() || this_user()) == oUser )
172 string msg = htmllib.quote_xml( args[2] );
173 msg = (geteuid() || this_user())->get_user_name() + ": "+ msg;
174 object grp = args[0]->get_creator();
175 string rcpt = (geteuid() || this_user())->get_user_name();
177 rcpt = grp->get_identifier();
178 send_message("<message type='chat' to=\""+name_on_server(oUser)+
179 "\" from=\""+ name_on_server(rcpt)+
180 "\"><body>"+msg+"</body></message>\n");
191 user = USER(sUserName);
192 if ( !objectp(user) )
194 roster = user->query_attribute(USER_FAVOURITES);
195 if ( !arrayp(roster) )
200 void handle_auth(string user, string pass, string|void digest)
202 object u = _Persistence->lookup_user(user);
203 if ( stringp(digest) )
204 DEBUG_JABBER("DIGEST="+digest+", MD5="+u->get_password()+"\n");
206 FATAL("Unable to find user " + user);
207 if ( objectp(u) && u->check_user_password(pass) ) {
209 tellListen = JabberListener(EVENT_TELL, u);
213 logoutListen = ([ ]);
216 foreach(get_roster(), object user) {
217 if ( user->status() < 0 ) continue;
219 if ( user->get_object_class() & CLASS_USER ) {
220 loginListen[user] = JabberListener(EVENT_LOGIN, user);
221 logoutListen[user] = JabberListener(EVENT_LOGIN, user);
223 else if ( user->get_object_class() & CLASS_GROUP ) {
225 JabberListener(EVENT_SAY, user->query_attribute(GROUP_WORKROOM));
228 // initial presence ?
231 send_iq_result(401, "Unauthorized");
238 array roster = get_roster();
239 string result = "<query xmlns=\"jabber:iq:roster\">\n";
241 if ( command->iq->type == "get" ) {
243 result += "<item jid=\""+name_on_server(u->get_identifier())+"\""+
244 " name=\""+u->get_identifier()+
245 "\" subscription=\"both\">"+
246 " <group>steam</group></item>";
248 result += "</query>";
249 send_iq_result(0, result);
252 else if ( command->iq->type == "set" ) {
253 string nick = get_nick(command->iq->item->jid);
254 string gname = command->iq->item->group;
255 if ( !stringp(gname) ) gname = "Friends";
257 //! TODO: add support for rooms
258 u = _Persistence->lookup_user(nick);
262 if ( command->iq->item->subscription == "remove" ) {
263 result += "<item jid=\""+name_on_server(u)+"\""+
264 " name=\""+u->get_identifier()+
265 "\" subscription=\"remove\">"+
266 " <group>steam</group></item>";
268 (geteuid() || this_user())->set_attribute(USER_FAVOURITES, roster);
269 if ( objectp(loginListen[u]) )
270 destruct(loginListen[u]);
271 if ( objectp(logoutListen[u]) )
272 destruct(logoutListen[u]);
276 // see if user is online
277 if ( search(roster, u) == -1 ) {
279 if ( u->get_object_class() & CLASS_USER &&
280 u->get_status() & CLIENT_FEATURES_CHAT )
282 loginListen[u] = JabberListener(EVENT_LOGIN, u);
283 logoutListen[u] = JabberListener(EVENT_LOGOUT, u);
285 if ( u->get_object_class() & CLASS_GROUP ) {
287 JabberListener(EVENT_SAY, u->query_attribute(GROUP_WORKROOM));
289 oUser->set_attribute(USER_FAVOURITES, roster);
291 result += "<item jid=\""+name_on_server(u)+"\""+
292 " name=\""+u->get_identifier()+"\" subscription='to'>" +
293 " <group>steam</group> </item>";
296 result += "</query>";
297 send_iq_result(0, result);
299 "<iq type=\"set\" to=\""+name_on_server(geteuid() || this_user())+"\">"+
301 if ( u->get_object_class() & CLASS_GROUP ||
302 u->get_status() & CLIENT_FEATURES_CHAT )
304 send_message("<presence from='"+name_on_server(u)+"' />\n");
313 string nick = get_nick(command->iq->to);
315 object u = _Persistence->lookup_user(nick);
317 string uname = u->query_attribute(USER_FULLNAME);
318 string gname,sname, email;
319 sscanf(uname, "%s %s", gname, sname);
321 email = u->query_attribute(USER_EMAIL);
323 send_message("<iq type=\"result\" from=\""+command->iq->to+"\" id=\""+
324 command->iq->id+"\">"+
325 "<vCard xmlns=\"vcard-temp\">\n"+
326 "<N><FAMILY>"+ sname + "</FAMILY>"+
327 "<GIVEN>"+gname+"</GIVEN>"+
329 "<NICKNAME>"+u->get_identifier()+"</NICKNAME>"+
334 "<EMAIL>"+email+"</EMAIL>"+
340 send_iq_result(400, "No Such User");
344 void handle_register()
346 if ( command->iq->type == "get" ) {
347 string uname = get_nick(command->iq->to);
348 object u = _Persistence->lookup_user(uname);
352 send_message("<iq type=\"result\" from=\""+command->iq->to+
353 "\" to=\""+name_on_server(geteuid() || this_user()) + "\" id=\""+
354 command->id->id+"\">\n"+
355 "<query xmlns=\"jabber:iq:register\">\n"+
361 send_message("<iq type=\"result\" from=\""+command->iq->to+
362 "\" to=\""+name_on_server(geteuid() || this_user()) + "\" id=\""+
363 command->id->id+"\">\n"+
364 "<query xmlns=\"jabber:iq:register\">\n"+
373 void handle_auth_init(string user)
375 if ( command->iq->type == "get" ) {
376 object uobj = USER(user);
377 if ( !objectp(uobj) )
378 send_message("<iq type=\"error\" from=\""+_Server->get_server_name()+
379 "\" id=\""+ command->iq->id+"\">\n"+
380 "<query xmlns=\"jabber:iq:auth\">\n"+
381 "<error type='cancel' code='409'>\n"+
382 " <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>\n"+
383 "</error>\n</query>\n</id>\n");
385 send_message("<iq type=\"result\" from=\""+user+ "\" id=\""+
386 command->iq->id+"\">\n"+
387 "<query xmlns=\"jabber:iq:auth\">\n"+
396 void handle_private()
404 object user = USER(sUserName);
405 if ( !objectp(user) ) {
406 send_message("<stream:error>\n"+
407 " <not-authorized />\n"
408 "</stream:error>\n");
416 if ( command->vCard ) {
419 if ( command->iq->query ) {
420 switch(command->iq->query->xmlns) {
421 case "jabber:iq:auth":
422 sUserName = command->iq->query->username->data;
423 if ( mappingp(command->iq->query->password) )
424 handle_auth(sUserName, command->iq->query->password->data);
425 else if ( mappingp(command->iq->query->digest) )
426 handle_auth(sUserName, 0, command->iq->query->digest->data);
428 handle_auth_init(sUserName);
430 case "jabber:iq:roster":
435 case "jabber:iq:register":
438 case "jabber:iq:agents":
442 send_message("<iq id='"+command->iq->id+"' type='result'>"+
443 "<query xmlns='jabber:iq:agents' /> </iq>\n");
446 case "jabber:iq:private":
456 string compose_html(mapping html)
459 foreach(indices(html), string idx) {
460 if ( stringp(html[idx]) )
462 else if ( mappingp(html[idx]) )
463 result += sprintf("<%s>%s</%s>", idx, compose_html(html[idx]), idx);
470 void handle_message()
472 string nick = command->message->to;
473 sscanf(nick, "%s@%*s", nick);
475 object u = MODULE_USERS->lookup(nick);
479 u = u->query_attribute(GROUP_WORKROOM);
481 MESSAGE("handle_message() to %s, %O", nick, u);
483 if ( stringp(command->message->body->data) )
484 msg = command->message->body->data;
485 else if ( stringp(command->message->html->data) )
486 msg = compose_html(command->message->html);
488 FATAL("Cannot get jabber message: %O\n", command);
490 if ( stringp(msg) && strlen(msg) > 0 && msg[0] == '=' ) {
491 msg = htmllib.unquote_xml(msg);
492 string result = execute(msg);
493 send_message("<message type='chat' to=\""+name_on_server(oUser)+
494 "\" from=\""+name_on_server(nick)+"\"><body>"+
495 htmllib.quote_xml(result)+"</body></message>\n");
505 void handle_presence()
507 if ( stringp(command->presence->to) ) {
508 string uname = get_nick(command->presence->to);
509 object user = USER(uname);
510 if ( !objectp(user) ) {
511 send_message("<presence from=\""+command->presence->to+"\" "+
512 "to=\""+name_on_server(geteuid() || this_user())+"\" "+
513 "type=\"unsubscribed\" />\n");
517 // TODO: handle subscription
518 if ( command->presence->type == "subscribe" ) {
519 send_message("<presence from=\""+name_on_server(user)+"\" "+
520 "to=\""+name_on_server(geteuid() || this_user())+"\" "+
521 "type=\"subscribed\" />\n");
522 array roster = get_roster();
523 if ( search(roster, user) == -1 ) {
524 loginListen[user] = JabberListener(EVENT_LOGIN, user);
525 logoutListen[user] = JabberListener(EVENT_LOGOUT, user);
526 roster += ({ user });
527 (geteuid() || this_user())->set_attribute(USER_FAVOURITES, roster);
529 if ( user->get_status() & CLIENT_FEATURES_CHAT )
530 send_message("<presence from=\""+name_on_server(user) + "\" />\n");
535 //send_message("<presence from=\""+name_on_server(geteuid() || this_user())+"\"/>\n");
536 foreach(get_roster(), object u) {
537 DEBUG_JABBER("PRESENCE: Roster %O", u);
538 if ( u == oUser ) continue;
540 if ( u->get_object_class() & CLASS_GROUP ||
541 u->get_status() & CLIENT_FEATURES_CHAT )
543 send_message("<presence from=\""+
544 name_on_server(u)+"\"/>\n");
550 void handle_command(string cmd)
553 DEBUG_JABBER("HANDLE_COMMAND: "+cmd+"\n"+sprintf("%O\n",command));
568 FATAL("Error in JABBER: handle_command():\n%O\O", err[0], err[1]);
569 send_message("<stream:error>\n"+
570 " <internal-server-error/>\n"
571 "</stream:error>\n");
576 private int data_callback(Parser.HTML p, string data)
578 if ( sizeof(queue) == 0 )
580 string name = queue[-1];
581 insertMap[name]["data"] = data;
595 private int tag_callback(Parser.HTML p, string tag)
598 mapping attr = ([ ]);
599 if ( tag[-2] == '/' ) {
603 attr += p->parse_tag_args(tag);
605 foreach(indices(attr), string a ) {
606 if ( a != "/" && attr[a] == a ) {
608 m_delete(attr, name);
613 if ( name == "stream:stream" ) {
616 else if ( name == "/stream:stream" ) {
619 else if ( name[0] == '/' ) {
620 if ( name[1..] == queue[-1] ) {
621 if ( sizeof(queue) == 1 ) {
623 handle_command(name[1..]);
626 queue = queue[..sizeof(queue)-2];
630 DEBUG_JABBER("Mismatched tag: " + name);
633 else if ( attr["/"] == "/" ) {
636 foreach(queue, string qtag) {
637 if ( mappingp(insertMap[qtag]) )
638 insertMap = insertMap[qtag];
640 insertMap[name] = attr;
641 if ( sizeof(queue) == 0 ) {
642 handle_command(name);
647 foreach(queue, string qtag) {
648 if ( mappingp(insertMap[qtag]) )
649 insertMap = insertMap[qtag];
651 insertMap[name] = attr;
660 void receive_message(string data)
662 DEBUG_JABBER("feeding: %s", data);
663 xmlParser->feed(data);
676 void send_message(string msg)
678 DEBUG_JABBER("MESSAGE(%O): %s", oUser, msg);
685 void create(object f)
688 xmlParser->_set_tag_callback(tag_callback);
689 xmlParser->_set_data_callback(data_callback);
694 string get_socket_name() { return "Jabber"; }
695 int get_client_features() { return CLIENT_FEATURES_ALL; }