jabber._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: jabber.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
18  */
19 inherit "/net/coal/login";
20 inherit "/kernel/coalsocket";
21 inherit "/net/base/cmd";
22  inherit Events.Listener;
23 #include <macros.h>
24 #include <database.h>
25 #include <attributes.h>
26 #include <events.h>
27 #include <client.h>
28 #include <classes.h>
29 class jabber : public login,coalsocket,cmd{
30 public:
31 
32 
33 
34 
35 
36 //#define JABBER_DEBUG
37 
38 #ifdef JABBER_DEBUG
39 #define DEBUG_JABBER(s, args...) werror("Jab: "+s+"\n", args)
40 #else
41 #define DEBUG_JABBER(s, args...)
42 #endif
43 
44 class JabberListener {
45 public:
46 
47  void create(int events, object obj) {
48  ::create(events, PHASE_NOTIFY, obj, 0, oUser);
49  obj->listen_event(this_object());
50  }
51 
52  void notify(int event, mixed args) {
53  notify_jabber(event, @args);
54  }
55  function get_callback() {
56  return notify;
57  }
58 
59  mapping save() { return 0; }
60 
61  string describe() {
62  return "JabberListener()";
63  }
64 }
65 
66 
67  array queue = ({ });
68  mapping command = ([ ]);
69  mapping insertMap;
70  mapping mRegisterKeys = ([ ]);
71 
72 // listener
73  JabberListener tellListen;
74  mapping loginListen = ([ ]);
75  mapping logoutListen = ([ ]);
76  mapping sayListen = ([ ]);
77 
78  string sUserName = "";
79 
80 #if constant(Parser.get_xml_parser)
81  Parser.HTML xmlParser = Parser.get_xml_parser();
82 #else
83  object xmlParser = 0;
84 #endif
85 
86 void open_stream()
87 {
88  send_message("<stream:stream from=\""+_Server->get_server_name()+
89  "\" xmlns=\"jabber:client\" "+
90  "xmlns:stream=\"http://etherx.jabber.org/streams\">");
91 }
92 
93 void close_stream()
94 {
95  send_message("</stream:stream>\n");
96  if ( objectp(oUser) )
97  oUser->disconnect();
98  foreach(values(logoutListen), object o)
99  destruct(o);
100  foreach(values(loginListen), object x)
101  destruct(x);
102  foreach(values(sayListen), object s)
103  destruct(s);
104  destruct(tellListen);
105  close_connection();
106 }
107 
108 void disconnect()
109 {
110  DEBUG_JABBER("Disconnecting");
111  if ( objectp(oUser) )
112  oUser->disconnect();
113  ::close_connection();
114 }
115 
116 void send_iq_result(int code, string|void desc)
117 {
118  if ( !stringp(desc) ) desc = "";
119 
120  if ( !stringp(command->iq->id) )
121  return;
122 
123  if ( code == 0 )
124  send_message("<iq type=\"result\" id=\""+command["iq"]["id"]+"\">\n"+
125  desc+"</iq>\n");
126  else
127  send_message("<iq type=\"result\" id=\""+command["iq"]["id"]+"\">\n"+
128  "<error code=\""+code+"\">"+desc+"</error>\n</iq>\n");
129 }
130 
131 string name_on_server(string|object user)
132 {
133  string n;
134 
135  if ( stringp(user) )
136  n = user;
137  else if ( objectp(user) )
138  n = user->get_identifier();
139  else
140  n = sUserName;
141 
142  return n + "@"+_Server->get_server_name();
143 }
144 
145 string get_nick(string name)
146 {
147  sscanf(name, "%s@%*s", name);
148  return name;
149 }
150 
151 void notify_jabber(int event, mixed ... args)
152 {
153  DEBUG_JABBER("notify("+event+", in %O): " + sprintf("%O\n", args), oUser);
154  switch( event ) {
155  case EVENT_LOGIN: {
156  send_message("<presence from=\""+name_on_server(geteuid() || this_user()) +"\"/>");
157  } break;
158  case EVENT_LOGOUT: {
159  send_message("<presence type=\"unavailable\" from=\""+
160  name_on_server(args[0]) +"\"/>");
161  } break;
162  case EVENT_TELL: {
163  string msg = htmllib.quote_xml( args[2] );
164  send_message("<message type='chat' to=\""+name_on_server(oUser)+
165  "\" from=\""+
166  name_on_server(geteuid() || this_user())+
167  "\"><body>"+msg+"</body></message>\n");
168  } break;
169  case EVENT_SAY: {
170  if ( (geteuid() || this_user()) == oUser )
171  return;
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();
176  if ( objectp(grp) )
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");
181  } break;
182  }
183 }
184 
185 
186 array get_roster()
187 {
188  array roster;
189  object user;
190 
191  user = USER(sUserName);
192  if ( !objectp(user) )
193  return ({ });
194  roster = user->query_attribute(USER_FAVOURITES);
195  if ( !arrayp(roster) )
196  roster = ({ });
197  return roster;
198 }
199 
200 void handle_auth(string user, string pass, string|void digest)
201 {
202  object u = _Persistence->lookup_user(user);
203  if ( stringp(digest) )
204  DEBUG_JABBER("DIGEST="+digest+", MD5="+u->get_password()+"\n");
205  if ( !objectp(u) )
206  FATAL("Unable to find user " + user);
207  if ( objectp(u) && u->check_user_password(pass) ) {
208  login_user(u);
209  tellListen = JabberListener(EVENT_TELL, u);
210  send_iq_result(0);
211 
212  loginListen = ([ ]);
213  logoutListen = ([ ]);
214  sayListen = ([ ]);
215 
216  foreach(get_roster(), object user) {
217  if ( user->status() < 0 ) continue;
218 
219  if ( user->get_object_class() & CLASS_USER ) {
220  loginListen[user] = JabberListener(EVENT_LOGIN, user);
221  logoutListen[user] = JabberListener(EVENT_LOGIN, user);
222  }
223  else if ( user->get_object_class() & CLASS_GROUP ) {
224  sayListen[user] =
225  JabberListener(EVENT_SAY, user->query_attribute(GROUP_WORKROOM));
226  }
227  }
228  // initial presence ?
229  }
230  else {
231  send_iq_result(401, "Unauthorized");
232  }
233 }
234 
235 void handle_roster()
236 {
237  object u;
238  array roster = get_roster();
239  string result = "<query xmlns=\"jabber:iq:roster\">\n";
240 
241  if ( command->iq->type == "get" ) {
242  foreach(roster, u) {
243  result += "<item jid=\""+name_on_server(u->get_identifier())+"\""+
244  " name=\""+u->get_identifier()+
245  "\" subscription=\"both\">"+
246  " <group>steam</group></item>";
247  }
248  result += "</query>";
249  send_iq_result(0, result);
250  return;
251  }
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";
256 
257  //! TODO: add support for rooms
258  u = _Persistence->lookup_user(nick);
259  if ( !objectp(u) )
260  u = GROUP(nick);
261 
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>";
267  roster -= ({ u });
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]);
273  }
274  else {
275  if ( objectp(u) ) {
276  // see if user is online
277  if ( search(roster, u) == -1 ) {
278  roster += ({ u });
279  if ( u->get_object_class() & CLASS_USER &&
280  u->get_status() & CLIENT_FEATURES_CHAT )
281  {
282  loginListen[u] = JabberListener(EVENT_LOGIN, u);
283  logoutListen[u] = JabberListener(EVENT_LOGOUT, u);
284  }
285  if ( u->get_object_class() & CLASS_GROUP ) {
286  sayListen[u] =
287  JabberListener(EVENT_SAY, u->query_attribute(GROUP_WORKROOM));
288  }
289  oUser->set_attribute(USER_FAVOURITES, roster);
290  }
291  result += "<item jid=\""+name_on_server(u)+"\""+
292  " name=\""+u->get_identifier()+"\" subscription='to'>" +
293  " <group>steam</group> </item>";
294  }
295  }
296  result += "</query>";
297  send_iq_result(0, result);
298  send_message(
299  "<iq type=\"set\" to=\""+name_on_server(geteuid() || this_user())+"\">"+
300  result+"</iq>\n");
301  if ( u->get_object_class() & CLASS_GROUP ||
302  u->get_status() & CLIENT_FEATURES_CHAT )
303  {
304  send_message("<presence from='"+name_on_server(u)+"' />\n");
305  }
306  }
307 }
308 
309 void handle_vcard()
310 {
311  mixed err;
312  // whom ???
313  string nick = get_nick(command->iq->to);
314 
315  object u = _Persistence->lookup_user(nick);
316  if ( objectp(u) ) {
317  string uname = u->query_attribute(USER_FULLNAME);
318  string gname,sname, email;
319  sscanf(uname, "%s %s", gname, sname);
320  err = catch {
321  email = u->query_attribute(USER_EMAIL);
322  };
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>"+
328  "<MIDDLE/></N>\n"+
329  "<NICKNAME>"+u->get_identifier()+"</NICKNAME>"+
330  "<TITLE/>"+
331  "<ROLE/>"+
332  "<TEL/>"+
333  "<ADR/>"+
334  "<EMAIL>"+email+"</EMAIL>"+
335  "</vCard>"+
336  "</iq>\n");
337 
338  }
339  else {
340  send_iq_result(400, "No Such User");
341  }
342 }
343 
344 void handle_register()
345 {
346  if ( command->iq->type == "get" ) {
347  string uname = get_nick(command->iq->to);
348  object u = _Persistence->lookup_user(uname);
349  if ( !objectp(u) )
350  u = GROUP(uname);
351  if ( objectp(u) ) {
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"+
356  "<registered />"+
357  "</query>\n"+
358  "</iq>\n");
359  }
360  else {
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"+
365  "<username />"+
366  "<password />"+
367  "</query>\n"+
368  "</iq>\n");
369  }
370  }
371 }
372 
373 void handle_auth_init(string user)
374 {
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");
384  else
385  send_message("<iq type=\"result\" from=\""+user+ "\" id=\""+
386  command->iq->id+"\">\n"+
387  "<query xmlns=\"jabber:iq:auth\">\n"+
388  "<username />"+
389  "<password />"+
390  "</query>\n"+
391  "</iq>\n");
392  }
393 }
394 
395 private:
396 void handle_private()
397 {
398 }
399 
400 public:
401 
402 int check_auth()
403 {
404  object user = USER(sUserName);
405  if ( !objectp(user) ) {
406  send_message("<stream:error>\n"+
407  " <not-authorized />\n"
408  "</stream:error>\n");
409  return 0;
410  }
411  return 1;
412 }
413 
414 void handle_iq()
415 {
416  if ( command->vCard ) {
417  handle_vcard();
418  }
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);
427  else
428  handle_auth_init(sUserName);
429  break;
430  case "jabber:iq:roster":
431  if ( !check_auth() )
432  return;
433  handle_roster();
434  break;
435  case "jabber:iq:register":
436  handle_register();
437  break;
438  case "jabber:iq:agents":
439  if ( !check_auth() )
440  return;
441  // deprecated anyway
442  send_message("<iq id='"+command->iq->id+"' type='result'>"+
443  "<query xmlns='jabber:iq:agents' /> </iq>\n");
444  break;
445 private:
446  case "jabber:iq:private":
447  if ( !check_auth() )
448  return;
449  handle_private();
450  break;
451  }
452  }
453 }
454 
455 protected:
456  string compose_html(mapping html)
457 {
458  string result = "";
459  foreach(indices(html), string idx) {
460  if ( stringp(html[idx]) )
461  result += html[idx];
462  else if ( mappingp(html[idx]) )
463  result += sprintf("<%s>%s</%s>", idx, compose_html(html[idx]), idx);
464  }
465  return result;
466 }
467 
468 public:
469 
470 void handle_message()
471 {
472  string nick = command->message->to;
473  sscanf(nick, "%s@%*s", nick);
474 
475  object u = MODULE_USERS->lookup(nick);
476  if ( !objectp(u) ) {
477  u = GROUP(nick);
478  if ( objectp(u) )
479  u = u->query_attribute(GROUP_WORKROOM);
480  }
481  MESSAGE("handle_message() to %s, %O", nick, u);
482  string msg;
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);
487  else
488  FATAL("Cannot get jabber message: %O\n", command);
489 
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");
496  return;
497  }
498 
499  if ( objectp(u) ) {
500  u->message(msg);
501  }
502 
503 }
504 
505 void handle_presence()
506 {
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");
514 
515  return;
516  }
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);
528  }
529  if ( user->get_status() & CLIENT_FEATURES_CHAT )
530  send_message("<presence from=\""+name_on_server(user) + "\" />\n");
531  }
532 
533  }
534  else {
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;
539 
540  if ( u->get_object_class() & CLASS_GROUP ||
541  u->get_status() & CLIENT_FEATURES_CHAT )
542  {
543  send_message("<presence from=\""+
544  name_on_server(u)+"\"/>\n");
545  }
546  }
547  }
548 }
549 
550 void handle_command(string cmd)
551 {
552  mixed err = catch {
553  DEBUG_JABBER("HANDLE_COMMAND: "+cmd+"\n"+sprintf("%O\n",command));
554  switch(cmd) {
555  case "presence":
556  handle_presence();
557  break;
558  case "iq":
559  handle_iq();
560  break;
561  case "message":
562  handle_message();
563  break;
564  }
565  command = ([ ]);
566  };
567  if ( err ) {
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");
572  }
573 }
574 
575 private:
576 private int data_callback(Parser.HTML p, string data)
577 {
578  if ( sizeof(queue) == 0 )
579  return 0;
580  string name = queue[-1];
581  insertMap[name]["data"] = data;
582  return 0;
583 }
584 
585 public:
586 
587 /**
588  *
589  *
590  * @param
591  * @return
592  * @see
593  */
594 private:
595 private int tag_callback(Parser.HTML p, string tag)
596 {
597  string name;
598  mapping attr = ([ ]);
599  if ( tag[-2] == '/' ) {
600  attr["/"] = "/";
601  tag[-2] = ' ';
602  }
603  attr += p->parse_tag_args(tag);
604 
605  foreach(indices(attr), string a ) {
606  if ( a != "/" && attr[a] == a ) {
607  name = a;
608  m_delete(attr, name);
609  break;
610  }
611  }
612 
613  if ( name == "stream:stream" ) {
614  open_stream();
615  }
616  else if ( name == "/stream:stream" ) {
617  close_stream();
618  }
619  else if ( name[0] == '/' ) {
620  if ( name[1..] == queue[-1] ) {
621  if ( sizeof(queue) == 1 ) {
622  queue = ({ });
623  handle_command(name[1..]);
624  }
625  else {
626  queue = queue[..sizeof(queue)-2];
627  }
628  }
629  else {
630  DEBUG_JABBER("Mismatched tag: " + name);
631  }
632  }
633  else if ( attr["/"] == "/" ) {
634  m_delete(attr, "/");
635  insertMap = command;
636  foreach(queue, string qtag) {
637  if ( mappingp(insertMap[qtag]) )
638  insertMap = insertMap[qtag];
639  }
640  insertMap[name] = attr;
641  if ( sizeof(queue) == 0 ) {
642  handle_command(name);
643  }
644  }
645  else {
646  insertMap = command;
647  foreach(queue, string qtag) {
648  if ( mappingp(insertMap[qtag]) )
649  insertMap = insertMap[qtag];
650  }
651  insertMap[name] = attr;
652  queue += ({ name });
653  }
654  return 0;
655 }
656 
657 public:
658 
659 protected:
660  void receive_message(string data)
661 {
662  DEBUG_JABBER("feeding: %s", data);
663  xmlParser->feed(data);
664 }
665 
666 public:
667 
668 /**
669  *
670  *
671  * @param
672  * @return
673  * @see
674  */
675 protected:
676  void send_message(string msg)
677 {
678  DEBUG_JABBER("MESSAGE(%O): %s", oUser, msg);
679  ::send_message(msg);
680 }
681 
682 public:
683 
684 protected:
685  void create(object f)
686 {
687  ::create(f);
688  xmlParser->_set_tag_callback(tag_callback);
689  xmlParser->_set_data_callback(data_callback);
690 }
691 
692 public:
693 
694 string get_socket_name() { return "Jabber"; }
695 int get_client_features() { return CLIENT_FEATURES_ALL; }
696 
697 
698 };