#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")) || (helper = getchild(node, "delay", "urn:xmpp:delay"))) {
		    string fmt = helper["@stamp"];
		    int *time = allocate(TM_MAX);
		    int res;

		    if (helper["@xmlns"] == "jabber:x:delay") {
			    // 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]);
		    } else {
			    // xep 0203 style CC-YY-MMDDThh:mm:ssZ
			    // 2002-09-10T23:05:37Z
			    res = sscanf(fmt, "%d-%2d-%2dT%2d:%2d:%2dZ", 
					 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 or the urm:xmpp:delay namespace 
		// this is a _status_presence
		o = summon_person(tu[UUser]);
		if ((helper = getchild(node, "x", "jabber:x:delay")) || (helper = getchild(node, "delay", "urn:xmpp: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;
}

