psyced/world/net/jabber/mixin_parse.c

940 lines
30 KiB
C

#include "jabber.h"
#include <net.h> // vim:set syntax=lpc
#include <uniform.h>
#include "presence.h"
#include <time.h>
#if !__EFUN_DEFINED__(idna_stringprep)
# echo Warning: idn support as recommended for XMPP is missing from LPC driver. will try lower_case() instead. usually works.
#endif
// necessary to implement a minimum set of commands for remote jabber users
// #undef USER_PROGRAM
// #undef MYNICK
// #define MYNICK <yournicknamevariable>
inherit NET_PATH "name";
volatile string origin;
volatile string nickplace;
volatile mixed place;
// shared_memory()
volatile mapping jabber2avail, avail2mc;
// we can leave out the variables if these are static anyway
#define cmdchar '/' // to remain in style with /me
#define actchar ':' // let's try some mudlike emote for jabber
#define NICKPLACE nickplace
#ifndef T
# define T(MC, TEXT) TEXT // do not use textdb
#endif
#include NET_PATH "usercmd.i"
void create() {
jabber2avail = shared_memory("jabber2avail");
avail2mc = shared_memory("avail2mc");
}
jabberMsg(XMLNode node, mixed origin, mixed *su, array(mixed) tu) {
string target, source;
string mc, data;
string body;
int isplacemsg;
mapping vars;
object o;
XMLNode helper;
mixed t;
target = node["@to"];
source = node["@from"];
unless(origin)
origin = XMPP + source;
#define MYORIGIN origin
// #define MYORIGIN XMPP + su[UUserAtHost]
unless(su) su = parse_uniform(origin);
origin = XMPP;
if (su[UUser]) {
origin += NODEPREP(su[UUser]) + "@";
}
origin += NAMEPREP(su[UHost]);
if (su[UResource]) {
origin += "/" + RESOURCEPREP(su[UResource]);
}
su = parse_uniform(origin);
if (node["/nick"] &&
node["/nick"]["@xmlns"] == "http://jabber.org/protocol/nick" &&
node["/nick"][Cdata]) {
sName(node["/nick"][Cdata]);
vars = ([ "_nick": MYNICK ]);
} else if (su[UUser]) {
sName(su[UUser]);
vars = ([ "_nick": MYNICK ]);
// if i turn on this line, stuff no longer arrives at local target
//if (su[UResource]) vars["_identification"] = origin;
// but.. this is the way it should work.. right?!
} else {
vars = ([ "_nick" : su[UString] ]);
}
#ifdef USE_THE_RESOURCE
if (su[UResource]) {
su[UResource] = RESOURCEPREP(su[UResource]);
// entity.c currently needs both _source_identification and
// _INTERNAL_identification to let it know the UNI is safe to use
// this is good because PSYC clients will get to see the
// _source_identification on the way out, too, while the fact
// the id can be trusted will be removed
vars["_INTERNAL_identification"] =
vars["_source_identification"] = XMPP + su[UUserAtHost];
vars["_INTERNAL_source_resource"] = su[UResource];
//vars["_location"] = origin;
P2(("UNI %O for UNR %O\n", vars["_source_identification"], source))
}
#endif
unless(tu) tu = parse_uniform(XMPP + target);
if (tu[UUser]) tu[UUser] = NODEPREP(tu[UUser]);
// TODO: probably we need nameprep here also
//
if (tu[UResource]) {
tu[UResource] = RESOURCEPREP(tu[UResource]);
vars["_INTERNAL_target_resource"] = tu[UResource];
}
isplacemsg = ISPLACEMSG(tu[UUser]);
/* I wonder if it makes sense to split this this into several functions,
* at least for IQ it would make sense, dito for presence
* also we should try to maximize shared code with jabber/user.c
*/
switch (node[Tag]) {
case "message":
D2( if (isplacemsg) D("place"); )
P2(("message %O from %O to %O\n",
node["@type"], origin, target))
#if 0
// this check is completly insufficient and doesn't work anyway...
if (node["/x"] && nodelistp(node["/x"])) // jabber:x:oob
vars["_uniform"] = node["/x"]["/url"];
#endif
switch (node["@type"]) {
case "error":
// watch out, do not create circular error messages
unless (o = summon_person(tu[UUser])) return;
if (node["/error"]) {
// _nick_target? why?
vars["_nick_target"] = origin;
if (xmpp_error(node["/error"],
"service-unavailable")) {
if (node["/text"]) {
vars["_text_XMPP"] = node["/text"][Cdata];
mc = "_failure_unavailable_service_talk_text";
data = "Talking to [_nick_target] is not possible: [_text_XMPP]";
} else {
mc = "_failure_unavailable_service_talk";
// data = "Talking to [_nick_target] is not possible. You may have to establish friendship first.";
// google talk sends this quite frequently:
// * when a friendship exchange hasn't been done
// * when a friend has gone offline
// and you never know if a message has been delivered to the
// recipient just the same! so here's a more accurate error message,
// effectively giving you less information, since that's what we have here.
data = "Message to [_nick_target] may not have reached its recipient.";
}
} else if (1) { // TODO: what was that error?
PT(("gateway TODO <error> in <message>: %O\n",
node["/error"]))
mc = "_error_unknown_name_user";
data = "Can't find [_nick_target].";
} else {
mc = "_jabber_message_error";
data = "[_nick] is sending you a jabber message error.";
// TODO: we can grab the error code / description
vars["_jabber_XML"] = innerxml;
}
sendmsg(o, mc, data, vars, origin);
}
break;
case "groupchat": // _message_public
if (node["/body"] && !pointerp(node["/body"]))
body = node["/body"][Cdata];
else {
body = 0;
P4(("no body in %O\n", node))
}
if (isplacemsg) {
// lots of these should be handled by placeRequest/input
// instead of sendmsg
// let usercmd know which room we are operating on..
unless (place = FIND_OBJECT(tu[UUser])) {
P0(("could not create place.. from %O to %O saying %O\n",
source, target, body))
break;
}
P2(("groupchat to %O\n", place))
// eg this should be a placeRequest("_set_topic", ...)
if (node["/subject"]
&& stringp(node["/subject"][Cdata])) {
#if 0
PT(("attempt by %O to change subject in %O lost: %O\n",
ME, place, node))
#else
vars["_topic"] = node["/subject"][Cdata];
sendmsg(place, "_request_set_topic", 0, vars, origin);
#endif
break;
}
PT(("input¹ %O\n", body))
if (stringp(body) && strlen(body)) {
#ifdef BETA
if (body[0] == '\n') body = body[1..];
#endif
if (body[0] == cmdchar) {
// '/ /usr' notation is a USER_PROGRAM feature
// so we have to redo it here
if (strlen(body) > 1 && body[1] == ' ') {
body = body[2..];
// fall thru
} else {
parsecmd(body[1..]);
return 1;
}
}
sendmsg(place, "_message_public", body,
vars, origin);
}
} else { // remote join stuff room message
o = summon_person(tu[UUser]);
// design decision: show them with full room nickname
if (su[UResource])
vars["_nick"] = su[UResource];
vars["_nick_place"] = vars["_INTERNAL_identification"] || origin;
#if __EFUN_DEFINED__(mktime)
if (helper = getchild(node, "x", "jabber:x:delay")) {
string fmt = helper["@stamp"];
int *time = allocate(TM_MAX);
int res;
// xep 0091 style CCYYMMDDThh:mm:ss
// 20080410T19:12:22
res = sscanf(fmt, "%4d%2d%2dT%2d:%2d:%2d",
time[TM_YEAR], time[TM_MON],
time[TM_MDAY], time[TM_HOUR],
time[TM_MIN], time[TM_SEC]);
if (res == 6) {
// mktime uses month from 0 to 11, december error fixed
time[TM_MON]--;
if ((res = mktime(time)) != -1) vars["_time_place"] = res;
}
}
#endif
#ifdef MUCSUC
// now using channels for unicast context emulation
vars["_context"] = XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser];
o = find_context(vars["_context"]);
if (!o) {
P0(("%O could not find the personal remotemuc for %O\n",
ME, vars["_context"]))
return;
}
P3(("xmpp castmsg %O\n", o))
#endif
if (!su[UResource] && node["/subject"]) {
/* a message from the room with subject (and in theory
* no body) is the topic
*/
vars["_topic"] = node["/subject"][Cdata];
#ifdef MUCSUC
o->castmsg(origin, "_status_place_topic", 0, vars);
//sendmsg(o, "_status_place_topic", 0, vars, origin);
} else {
o->castmsg(origin, "_message_public", body, vars);
//sendmsg(o, "_message_public", body, vars, origin);
#else
sendmsg(o, "_status_place_topic", 0, vars, origin);
} else {
sendmsg(o, "_message_public", body, vars, origin);
#endif
}
// innerxml pass-thru
// vars["_jabber_XML"] = innerxml;
// sendmsg(o, "_jabber_message_groupchat", 0, vars, origin);
}
break;
case 0: // _message_private which may have a subject
case "chat": // _message_private
default:
if (isplacemsg) {
#if 1 // STRICTLY UNFRIENDLY NON-FUN TREATMENT
sendmsg(XMPP + source, "_failure_unsupported_function_whisper",
"Routing private messages through groupchat managers is dangerous to your privacy and therefore disallowed. Please communicate with the person directly.",
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source ]),
ME);
#else // MAKE FUN OF ST00PID JABBER USERS VARIANT
// handle this by doing "flüstern" in room ;)
// <from> whispers to <to>: <cdata>
P0(("private message in place.. from %O to %O\n",
source, target))
// stimmt das? egal..
o = FIND_OBJECT(tu[UUser]);
vars["_nick_target"] = tu[UResource];
sendmsg(o, "_message_public_whisper",
// "[_nick] tries to whisper to [_nick_target] but it fails",
node[Cdata], vars, origin);
// cmd("/whisper", ...?)
#endif
} else if (!tu[UUser]) {
// stricmp is better than lower_case only when both sides
// have to be lowercased..
if (lower_case(tu[UResource]) == "echo") {
sendmsg(origin, "_message_private",
node["/body"][Cdata],
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source ]), ME);
} else if (node["/body"]
&& node["/body"][Cdata][0] != cmdchar) {
// monitor_report will log this to a file
// if no admin is listening
monitor_report("_request_message_administrative",
sprintf("%O wants to notify the administrators of %O", origin, node["/body"][Cdata]));
}
} else {
// no relaying allowed, so we ignore hostname
o = summon_person(tu[UUser]);
// xep 0085 typing notices - we even split active into a separate message
// for now. could be sent as a flag
if ((node[t="/composing"] || node[t="/active"] ||
node[t="/paused"] ||node[t="/inactive"] ||node[t="/gone"]) &&
node[t]["xmlns"] == "http://jabber.org/protocol/chatstates") {
// ...
sendmsg(o, "_notice_typing_" + t[1..], 0, vars);
}
// there are some messages which dont have a body
// we dont care about those
unless (node["/body"]) return;
ASSERT("Cdata", mappingp(node["/body"])
&& stringp(node["/body"][Cdata]), node)
body = node["/body"][Cdata];
if (strlen(body) && body[0] == cmdchar) {
body = body[1..];
if (abbrev("me ", body)) {
// doesn't cmd() handle this?
vars["_action"] = body[3..];
body = 0;
#ifdef USERCMD_IN_JABBER_CONVERSATION
} else {
// this doesn't take care of '/ /usr' notation!
parsecmd(body);
break;
#else
// fall thru
// the /bin/whatever will be treated as normal text
// so nusse is happy
#endif
}
}
if (helper = getchild(node, "x", "jabber:x:signed")) {
vars["_signature"] = helper[Cdata];
vars["_signature_encoding"] = "base64";
}
if (helper = getchild(node, "x", "jabber:x:encrypted")) {
vars["_data_openpgp"] = helper[Cdata];
// syntactical note: i would prefer to have this var
// called _data_openpgp:_encoding
vars["_encoding_data_openpgp"] = "base64";
mc = "_notice_private_encrypt_gpg";
// well... we need to put this stuff here and cant
// have it in the textdb...
// I would appreciate if we could do something like
// body = 0 and the fmt would be fetched from the
// textdb...
// also, this eludes the users language setting
// (this problem also occurs with presence
// notifications)
body = "openpgp encrypted message data follows\n"
"--- BEGIN OPENPGP BLOCK ---\n"
"[_data_openpgp]\n"
"--- END OPENPGP BLOCK ---";
};
// shouldn't we use /tell?
sendmsg(o, mc || "_message_private", body,
vars, origin);
}
; // break??
}
break;
case "presence":
if (!isplacemsg && getchild(node, "x", "http://jabber.org/protocol/muc#user")) {
isplacemsg = 2;
}
D2( if (isplacemsg) D("place"); )
P2(("presence %O from %O to %O\n",
node["@type"],
XMPP + source,
target))
// su = parse_uniform(XMPP + source);
// see also: XMPP-IM §2.2.1 Types of Presence
switch (node["@type"]) {
case "error":
// TODO:
// for now we ignore it at least
// so there wont be circular error messages
if (tu[UUser]) {
o = summon_person(tu[UUser]);
// the following should catch errors - in theory, requires testing
if (o) {
int cb_ret;
mixed err;
err = catch(
cb_ret = o->execute_callback(node["@id"], ({ vars["_INTERNAL_identification"], vars, node }) )
);
if (err) {
P0(("%O caught error during callback execution: %O\n", ME, err))
}
if (err || cb_ret) {
return 1;
}
}
}
if (tu[UResource]) {
// innerxml
vars["_jabber_XML"] = innerxml;
//sendmsg(o, "_jabber_presence_error", 0, vars, origin);
P1(("%O presence error. innerxml proxy to %O please: %O\n",
ME, node["@to"], innerxml))
}
break;
case "subscribe": // _request_friendship
if (isplacemsg) {
// autojoins dont work that way - what are
// those clients (ichat, gaim) trying to do?
// what's the appropriate stanza error?
// btw, text elemnent in stanzas errors SHOULD
// NOT be displayed to the user (see rfc3920 §9.3)
P2(("%O encountered presence %O for place %O\n",
ME, node["@type"], tu[UUser]));
o = FIND_OBJECT(tu[UUser]);
if (o->qNewsfeed())
sendmsg(origin, "_notice_friendship_established", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_source_jabber_bare" : target,
"_INTERNAL_target_jabber" : source ]),
ME);
else
sendmsg(origin, "_error_unsupported_method_request_friendship", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source]),
ME);
return;
}
unless(tu[UResource]) {
o = summon_person(tu[UUser]);
if (su[UResource]) {
P0(("encountered _request_friendwith with resource from %O to %O\n", source, target))
// return;
}
sendmsg(o, "_request_friendship", 0, vars, MYORIGIN);
} else {
// not sure if that's valid.. so let's look out for it
P0(("%O Surprise! Encountered friendship w/out resource: %O\n",
ME, node))
}
break;
case "subscribed": // _notice_friendship_established
if (isplacemsg) {
P2(("%O encountered presence %O for place %O\n",
ME, node["@type"], tu[UUser]));
sendmsg(origin, "_error_unsupported_method_notice_friendship_established", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source]),
ME);
return;
}
unless(tu[UResource]) {
o = summon_person(tu[UUser]);
if (su[UResource]) {
P0(("encountered _notice_friendship_established with resource from %O to %O\n", source, target))
// return;
}
sendmsg(o, "_notice_friendship_established", 0, vars, MYORIGIN);
} else {
// not sure if that's valid
}
break;
case "unsubscribe": // _notice_friendship_removed
if (isplacemsg) {
// TODO: wouldn't it be better to use _jabber_presence_error
// here in conjunction with _jabber_XML?
//
// like for subscribe, this might be useful for newsfeed
// if place->qNewsfeed() schicke ein unsubscribed zurueck
o = FIND_OBJECT(tu[UUser]);
if (o->qNewsfeed())
sendmsg(origin, "_notice_friendship_established", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_source_jabber_bare" : target,
"_INTERNAL_target_jabber" : source ]),
ME);
else
sendmsg(origin, "_error_unsupported_method_notice_friendship_removed", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source]),
ME); // should it be tu[UString] instead? TODO
}
/*
* mh... this may be one-sided... but PSYC
* does not have one-sided subscription
* so... fall thru
*/
case "unsubscribed": // _notice_friendship_removed
if (isplacemsg) {
// ignore it
} else {
unless (o = summon_person(tu[UUser])) return;
vars["_possessive"] = "the";
if (su[UResource]) {
P0(("encountered _notice_friendship_removed with resource from %O to %O\n", source, target))
// return;
}
sendmsg(o, "_notice_friendship_removed", 0, vars, MYORIGIN);
}
break;
case "unavailable": // _notice_presence_absent / _notice_place_leave
if (isplacemsg == 1) {
o = FIND_OBJECT(tu[UUser]);
#ifndef DONT_REWRITE_NICKS
vars["_nick_local"] = tu[UResource]; // it's a matter of case
#endif
sendmsg(o,
#ifdef SPEC
"_request_context_leave"
#else
"_request_leave"
#endif
, 0, vars, origin);
} else if (isplacemsg == 2) { // remote join stuff
o = summon_person(tu[UUser]);
vars["_nick"] = su[UResource];
vars["_nick_place"] = vars["_INTERNAL_identification"] || origin;
#ifdef MUCSUC
vars["_context"] = XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser];
#else
vars["_context"] = vars["_nick_place"];
#endif
if (o && o->execute_callback(node["@id"], ({ o, vars, node }))){
return 1;
}
#ifdef MUCSUC
o = find_context(XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser]);
if (o)
o->castmsg(origin, "_notice_place_leave", 0, vars);
else {
P0(("%O could not find the personal remotemuc for %O bis\n",
ME, vars["_context"]))
}
#else
sendmsg(o, "_notice_place_leave_unicast", 0, vars, origin);
#endif
} else {
#ifdef AVAILABILITY_OFFLINE
o = summon_person(tu[UUser]);
// http://www.psyc.eu/presence
vars["_degree_availability"] = AVAILABILITY_OFFLINE;
# ifdef CACHE_PRESENCE
persistent_presence(XMPP + su[UUserAtHost],
AVAILABILITY_OFFLINE);
# endif
vars["_description_presence"] =
(node["/status"] && node["/status"][Cdata]) ?
node["/status"][Cdata] : ""; // "Get psyced!";
vars["_INTERNAL_XML_description_presence"] =
xmlquote(vars["_description_presence"]);
vars["_INTERNAL_mood_jabber"] = "neutral";
sendmsg(o, "_notice_presence_absent", 0,
vars, origin);
#endif
}
break;
case "probe":
if (isplacemsg) {
// this is actually not an error with newsfeed
// being subscribable
P2(("%O encountered presence %O for place %O\n",
ME, node["@type"], tu[UUser]));
} else {
// probe SHOULD only be generated by server but gmail
// sends it from a generated resource string. also jabber.org
// let's clients send it occasionally
o = summon_person(tu[UUser]);
sendmsg(o, "_request_status_person", 0,
vars, origin);
// XMPP + su[UUserAtHost]);
// maybe we can fix gmail presence by passing the UNR
// instead of the UNI in source
}
break;
default: // this is bad!
P2(("jabber presence isplacemsg %O\n", isplacemsg))
if (isplacemsg == 1) {
// TODO: houston... there is no way to
// decide whether this is a join or a
// status change... so the current
// behaviour of the rooms will send member
// list and history on each status change...
#if 0
// was not that a good idea...
if (node["/status"]) {
P2(("skipping status change in place\n"))
return;
}
#endif
if (helper = getchild(node, "x", "http://jabber.org/protocol/muc")) {
if (helper["/password"])
vars["_password"] = helper["/password"][Cdata];
if (helper["/history"]) {
// FIXME: support for other modes
if (t = helper["/history"]["@maxstanzas"])
vars["_amount_history"] = t;
}
}
o = FIND_OBJECT(tu[UUser]);
// lets see, if it works with lower_case
// it seems clients dont care about the case...
// but at least normal muc components care about case
// did i mention that muc is silly?
// if (lower_case(vars["_nick"]) != lower_case(tu[UResource]))
// yes! this is a good use for stricmp! ;)
// YACK!!! this does not work as intended.
// lynx, fix it please!!!
// hm.. the definition of stricmp is inverted.. oops
#ifdef DONT_REWRITE_NICKS
if (stricmp(vars["_nick"], tu[UResource])) {
// as everything else is much too complicated:
sendmsg(XMPP + source, "_error_unavailable_nick_place", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source ]),
o);
return;
}
#else
vars["_nick_local"] = tu[UResource]; // it's a matter of case
#endif
// if (node["/show"]) {
// then it should be a availability change
// yet... are there possibly clients that try sending
// this upon the initial enter?
// -- yes, if they're in global away and try to join
// did I mention that muc is a silly protocol?
// }
P4(("_request_enter from %O to %O: %O\n", ME, o, vars))
// dont send me a memberlist if i am a member already
#ifndef _limit_amount_history_place_default
# define _limit_amount_history_place_default 5
#endif
unless(vars["_amount_history"])
vars["_amount_history"] = _limit_amount_history_place_default;
sendmsg(o,
#ifdef SPEC
"_request_context_enter"
#else
"_request_enter"
#endif
"_again", 0,
vars, origin);
} else if (isplacemsg == 2) { // remote join stuff
#ifdef MUCSUC
object ctx = find_context(XMPP+ su[UUserAtHost]
+MUCSUC_SEP+ tu[UUser]);
if (!ctx) {
P0(("%O could not find the remotemuc for %O tris\n",
ME, vars["_context"]))
return;
}
#endif
o = summon_person(tu[UUser]);
vars["_nick"] = su[UResource];
vars["_nick_place"] = vars["_INTERNAL_identification"] || origin;
#ifdef MUCSUC
if (ctx) // get memberlist from remote slave
vars["_list_members"] = vars["_list_members_nicks"] = ctx->qMembers() + ({ tu[UUser] });
if (o && o->execute_callback(node["@id"], ({ vars["_context"], vars, node }))) return 1;
m_delete(vars, "_list_members");
m_delete(vars, "_list_members_nicks");
if (o = ctx)
o->castmsg(origin, "_notice_place_enter", 0, vars);
else {
// this can happen when joining
PT(("%O could not find the remotemuc for %O (yet)\n",
ME, vars["_context"]))
}
# else
vars["_context"] = vars["_nick_place"];
if (o && o->execute_callback(node["@id"], ({ vars["_INTERNAL_identification"], vars + ([ "_list_members" : 0, "_list_members_nicks" : 0 ]), node }))) return 1;
// comes with a faked _context for logic in user.c
sendmsg(o, "_notice_place_enter_unicast", 0, vars, origin);
#endif
} else {
int isstatus;
/* see http://www.psyc.eu/presence */
// if the node contains a x element in the
// jabber:x:delay namespace this is a
// _status_presence_here
o = summon_person(tu[UUser]);
if (helper = getchild(node, "x", "jabber:x:delay")) {
isstatus = 1;
}
// if (!intp(isstatus)) {
// parse jabbertime and convert to timestamp
// we also know since when he has
// been available TODO
// }
vars["_description_presence"] =
(node["/status"] && node["/status"][Cdata]) ?
node["/status"][Cdata] : ""; // "Get psyced!";
vars["_INTERNAL_XML_description_presence"] =
xmlquote(vars["_description_presence"]);
vars["_degree_availability"] = jabber2avail[node["/show"]
&& node["/show"][Cdata]];
// this message is too verbose, let's put in into
// debug log so we can see it in relation to the
// bug we experienced before (does it still exist?)
// PV(("p-Show in %O, origin %O, isstatus %O, vars %O\n",
// ME, origin, isstatus, vars));
// the info hasn't proved useful :(
vars["_INTERNAL_quiet"] = 1;
vars["_INTERNAL_mood_jabber"] = "neutral";
sendmsg(o, (isstatus? "_status_presence": "_notice_presence")
+ (avail2mc[vars["_degree_availability"]] || "_here"), 0,
vars, origin);
#ifdef CACHE_PRESENCE
persistent_presence(XMPP + su[UUserAtHost],
vars["_degree_availability"]);
#endif
}
break;
}
break;
case "iq":
{
mixed iqchild = getiqchild(node);
string xmlns = iqchild ? iqchild["@xmlns"] : 0;
// TODO: maybe this should be handled by several functions
// iq_get, iq_set, iq_result, iq_error
t = node["@type"];
if (t == "result" || t == "error") {
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
if (o && o->execute_callback(node["@id"], ({ origin, vars, node })))
return 1;
vars["_tag_reply"] = node["@id"];
} else {
vars["_tag"] = node["@id"];
}
// mh... we don't get that child with a result if the
// entity that we have asked does not have a vCard
// cool protocol!
switch(xmlns) {
case "vcard-temp":
{
mixed mvars;
// innerxml note: only result is a possible candidate
switch (t) {
case "result":
// this should not happen any longer since _request_description is chained
P3(("vCard result from %O to %O\n", source, target))
// only do the work if we find a rcpt
unless (o = summon_person(tu[UUser])) return;
mvars = convert_profile(node["/vCard"], "jCard");
PT(("extracted from vCard: %O\n", mvars))
mvars["_nick"] = su[UUser] || origin;
sendmsg(o, "_status_description_person", 0, mvars, origin);
break;
case "get":
P3(("vCard request from %O to %O\n",
source, target))
// target must be a 'bare' jid, but hey... we dont
// care about those rules anyway
if (isplacemsg) return;
if (tu[UResource]) return;
unless (tu[UUser]) return;
o = summon_person(tu[UUser]);
unless (o) return; // TODO
sendmsg(o, "_request_description_vCard", 0, vars, origin);
break;
case "set":
// a remote entity trying to do a set? haha!
// just be gentle and ignore it
P0(("%O Surprise! Encountered vCard set: %O\n", ME, node))
break;
case "error":
// this should not happen any longer since _request_description is chained
if (node["/error"]) {
unless (o = summon_person(tu[UUser])) {
// watch out, do not create circular error messages
P0(("%O vCard error from %O to %O\n",
ME, source, target))
return;
}
vars["_nick_target"] = MYORIGIN; // should be origin probably
if (xmpp_error(node["/error"],
"service-unavailable")) {
mc = "_failure_unavailable_service_description";
} else {
mc = "_error_unknown_name_user";
}
sendmsg(o, mc, 0, vars, origin);
}
break;
}
break;
}
case "http://jabber.org/protocol/disco#info":
if (iqchild["@node"])
vars["_target_fragment"] = iqchild["@node"];
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
switch(node["@type"]) {
case "get":
sendmsg(o, "_request_list_feature", 0, vars, origin);
break;
case "set": // doesnt make sense
case "result": // handled by callback usually
case "error": // dito
break;
}
break;
case "http://jabber.org/protocol/disco#items":
if (iqchild["@node"])
vars["_target_fragment"] = iqchild["@node"];
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
switch(node["@type"]) {
case "get":
sendmsg(o, "_request_list_item", 0, vars, origin);
break;
case "set": // doesnt make sense
case "result": // handled by callback usually
case "error": // dito
break;
}
break;
case "jabber:iq:version":
switch(t) {
case "get":
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
PT(("sending _request_version to %O\n", o))
sendmsg(o, "_request_version", 0, vars, origin);
break;
case "set":
// UHM???
P0(("encountered jabber:iq:version set\n"))
break;
case "result":
case "error":
P0(("got jabber:iq:version result/error without tag\n"))
break;
}
break;
case "jabber:iq:last":
switch(t) {
case "get":
if (isplacemsg || is_localhost(lower_case(target)))
o = "/" + (tu[UResource] || "");
else
o = summon_person(tu[UUser]);
sendmsg(o, "_request_description_time", 0, vars, origin);
break;
case "set":
break;
case "result":
o = summon_person(tu[UUser]);
vars["_time_idle"] = node["/query"]["@seconds"];
sendmsg(source, "_status_description_time", 0, vars, origin);
break;
case "error":
break;
}
break;
case "urn:xmpp:ping":
if (tu[UUser])
o = FIND_OBJECT(tu[UUser]);
else
o = "/" + (tu[UResource] || "");
switch(t) {
case "get":
case "set": // I dont know why xep 0199 uses set... its a request
sendmsg(o, "_request_ping", 0, vars, origin);
break;
break;
case "result": // caught by tagging
break;
case "error": // caught by tagging
break;
}
break;
default:
// isn't this dangerous now that we send a resource
if (tu[UResource]) {
vars["_jabber_XML"] = innerxml;
o = summon_person(tu[UUser]);
sendmsg(o, "_jabber_iq_"+ t,
"[_source] is sending you a jabber iq "+t, vars, origin);
} else {
switch(t) {
case "get":
case "set":
// see XMPP-IM §2.4
// (whereas we are rather recipient than router)
// send service-unavailable stanza error
sendmsg(origin, "_error_unsupported_method", 0,
([ "_INTERNAL_source_jabber" : target,
"_INTERNAL_target_jabber" : source,
"_tag_reply" : node["@id"] ]));
break;
case "result":
// usually we dont do requests where we dont
// understand the answer
// hence this is usually caught by TAGGING
P0(("%O iq result from %O to %O\n", ME, source, target))
break;
case "error":
// dont create circular error messages and hence: ignore
P0(("%O iq error from %O to %O\n", ME, source, target))
break;
default:
P0(("%O ignores unknown iq: %O\n", ME, t))
break;
}
}
break;
}
break;
}
default:
// mh... this might be interesting...
break;
}
return 1;
}