psyced/world/net/library.i

1199 lines
36 KiB
OpenEdge ABL

// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: library.i,v 1.344 2008/09/12 15:54:38 lynx Exp $
#include <net.h>
#include <services.h>
#include <person.h>
#include <uniform.h>
#ifdef _uniform_node
# define myUNL _uniform_node
#else
volatile string myUNL;
#endif
volatile string myUNLIP;
volatile string myLowerCaseHost;
volatile mapping targets = ([]);
volatile mapping schemes = ([]);
volatile mapping services = ([]);
#ifdef CACHE_PRESENCE
// volatile until we have a logic which at boot-time figures out if we
// have been down only a few seconds (thus keep the presence data) or
// if it was longer than that (thus delete the cache)
volatile mapping presence_cache = ([ ]);
#endif
/* used for things that have a context but are no places
* could possible be used to keep room members as well
* so they are persistent during reloads
* if the index is a local user this will contain the
* group of his online friends
*/
volatile mapping contexts = ([]);
// in lack of a better name..
mapping confmap = ([]);
// system net/queue of outgoing messages per circuit
// shared between circuits but kept here for storage between reboots
mapping systemQ;
// protos
//string legal_name(string n);
//string legal_mailto(string a);
int psyc_sendmsg(mixed target, string method, mixed data, mapping vars,
int showingLog, mixed source, array(mixed) uniform);
// register a delivery object for a UNL
// first argument *must* be lower_case
varargs int register_target(string uniform, vaobject handler, vaint shy) {
PROTECT("REGISTER_TARGET")
#if DEBUG > 0
unless (uniform)
raise_error("register_target without uniform\n");
#endif
#if 0
if (SERVER_UNIFORM == uniform)
raise_error("register_target for root!?\n");
#endif
if (targets[uniform]) {
D2( unless (handler) handler = previous_object();
if (targets[uniform] != handler)
// TODO:: way higher debug level here!
PP(("register_target: %O(%O) wants to register %O, "
"but it already belongs to %O(%O), "
"replacing handler.\n",
handler, query_ip_name(handler), uniform,
targets[uniform], query_ip_name(targets[uniform]))); )
if (shy) return 2;
}
unless (handler) handler = previous_object();
P2(("register_target(%O) by %O\n", uniform, handler))
targets[uniform] = handler;
#if 0 // this shouldn't be necessary TODO
uniform = lower_case(uniform);
if (targets[uniform]) return 1;
targets[uniform] = handler;
#endif
return 1;
}
object find_target_handler(string target) { return targets[target]; }
// LOCALHOST STUFF
string query_server_uniform() { return myUNL; } // proto.h!
string query_server_uniform_ip() { return myUNLIP; }
string my_lower_case_host() { return myLowerCaseHost; }
static varargs void ready_freddie(vamixed ip) {
object root;
mixed t, h;
// why PROTECT("SET_MY_IP") a static function?
#ifndef __HOST_IP_NUMBER__
// once is not enough.. what if the dsl line crashes and now has a new one?
// we could be so smart not to need restarts! we don't use myIP for all that
// much.. so maybe it's fine just ignoring it.. as long as we know all of
// our hostnames..
unless (stringp(ip)) raise_error("Cannot resolve my own hostname!\n");
myIP = ip;
register_localhost(myLowerCaseHost, ip);
myUNLIP = "psyc://"+ ip;
if (query_udp_port() != PSYC_SERVICE)
myUNLIP += ":" + query_udp_port();
myUNLIP += "/";
D("Dynamic Server UNLs: "+ myUNL +" and "+ myUNLIP +"\n");
#endif
#ifndef __PIKE__
// we need to load the root object async from create()
// so it can use the library itself. that's why we first
// register all targets to the master, then remap them.
root = load_object(NET_PATH "root");
psyc_name(root, "");
mapeach(t, h, targets) {
if (h == master) targets[t] = root;
}
D("»»» Root entity ready.\n");
P4(("targets remapped to root entity: %O\n", targets))
#endif
}
// no way! you can't have a msg() in library because lastlog's call on msg()
// ends up here instead of resolving into a user->msg(). ok, you can fix
// that, but essentially there is a risk of breaking some function calls
// somewhere if you define popular lfuns also in the library.
//
//int msg(mixed source, string mc, mixed data, mapping vars, mixed target) {
// raise_error("got msg() in library!\n");
//}
static void create() {
PROTECT("CREATE")
#ifndef __PIKE__
master = previous_object();
restore_object(DATA_PATH "library");
systemQ = qCreate(); // should all entity queues be persistent
// instead of having q AND systemQ here?
#endif // __PIKE__
#ifdef TESTSUITE
ME->base64_self_test();
if (md5("foobar") != "3858f62230ac3c915f300c664312c63f")
raise_error("MD5 is br0ken!!11!!!\n");
# ifndef _flag_disable_authentication_digest_MD5
sasl_test();
# endif
//P4(("%O\n", make_json( ([ 7:"33\n44\t21", "!":93 ]) )))
printf("Testing make_json: Is %O equal to %O ?\n",
make_json( ([ "7":"33\n44\t21", "!":93, "struct":([ "yeh":({1,2}) ]) ]) ),
"{\"7\":\"33\\n44\\t21\",\"!\":93,\"struct\":{\"yeh\":[1,2]}}");
printf("Testing parse_json: Is %O equal to %O ?\n",
parse_json( "{\"7\":\"33\\n44\\t21\",\"!\":93,\"struct\":{\"yeh\":[1,2]}}"),
([ "7":"33\n44\t21", "!":93, "struct":([ "yeh":({1,2}) ]) ]) );
#endif
#ifdef PRO_PATH
ME->pro_create();
#endif
#ifdef MUDOS
D("Trying to run under MUDOS. Probably won't get very far..\n");
#endif
#ifdef __LDMUD__
# ifndef __psyclpc__
# echo Warning: You aren't using a psyclpc driver. This may cause trouble.
# else
# ifdef __NO_SRV__
# echo Warning: DNS SRV is unavailable.
# ifdef JABBER_PATH
# echo ... XMPP will not work correctly!!
# endif
# endif
# endif
//# echo Running under LDMUD. Good choice for a driver.
//# if !__EFUN_DEFINED__(errno)
//# echo Warning: This driver is missing the errno function.
//# endif
# if !__EFUN_DEFINED__(net_connect)
# echo Warning: This driver is missing the net_connect function.
# endif
# if !__EFUN_DEFINED__(convert_charset)
# echo Warning: This driver is missing iconv (convert_charset) support.
# ifdef JABBER_PATH
# echo ... XMPP will not work correctly!!
# endif
# endif
#endif
#ifdef AMYLAAR
# if __EFUN_DEFINED__(lambda)
D("Running under Amylaar LPMUD. You should upgrade to psyclpc.\n");
# else
D("Unrecognized LPC driver. Using Amylaar settings.\n");
# endif
#endif
#ifdef VOLATILE
D("VOLATILE flag set: Server will not save any data.\n");
#endif
myLowerCaseHost = lower_case(SERVER_HOST);
register_localhost(myLowerCaseHost);
#ifdef __PIKE__
//debug_write("Creating psyced library in Pike.\n");
#else
register_target("");
register_target("/");
register_target(myLowerCaseHost);
#endif
#ifdef LOCAL_HOSTS // compatible to the other stuff in hosts.h
# echo Setting up local host aliases.
foreach(string x : ({ LOCAL_HOSTS }))
if (index(x, '.') > 0) register_localhost(lower_case(x));
else debug_message("Invalid local hostname '"+x
+"' in #define LOCAL_HOSTS\n");
#else
# ifdef VIRTUAL_HOSTS // is this being used anywhere?
foreach(string x : explode(VIRTUAL_HOSTS, " "))
if (index(x, '.') > 0) register_localhost(lower_case(x));
else debug_message("Invalid virtual hostname '"+x
+"' in #define VIRTUAL_HOSTS\n");
# endif
#endif
#if SYSTEM_CHARSET != "UTF-8"
D("»»» System charset: " SYSTEM_CHARSET "\n");
#endif
#ifdef myUNL
unless (trail("/", myUNL)) {
debug_message("*** Invalid server uniform: '"+ myUNL
+"' has to end in a '/' ***\n");
shutdown();
}
#else
myUNL = "psyc://"+ myLowerCaseHost;
if (query_udp_port() != PSYC_SERVICE)
myUNL += ":" + query_udp_port();
register_target(myUNL);
myUNL += "/";
register_target(myUNL);
#endif
#ifdef __HOST_IP_NUMBER__
myUNLIP = "psyc://"+ __HOST_IP_NUMBER__;
if (query_udp_port() != PSYC_SERVICE)
myUNLIP += ":" + query_udp_port();
register_target(myUNLIP);
myUNLIP += "/";
register_target(myUNLIP);
# if __HOST_NAME__ != SERVER_HOST
D("»»» Using IP# " __HOST_IP_NUMBER__ " known as " __HOST_NAME__ " but given as " SERVER_HOST ".\n");
# else
D("»»» Using IP# " __HOST_IP_NUMBER__ " known as " __HOST_NAME__ ".\n");
# endif
D("»»» Static Server UNLs: "+ myUNL +" and "+ myUNLIP +"\n");
// the callout delay is too long.. maybe we should plug this
// into some other place? sigh.
call_out(#'ready_freddie, 0);
#else
//register_localhost(myLowerCaseHost, "127.0.0.1"); // inbetween...
# ifndef __PIKE__
dns_resolve(myLowerCaseHost, #'ready_freddie);
# endif
#endif
#ifdef JABBER_PATH
register_target("xmpp:"+ myLowerCaseHost);
# ifdef _host_XMPP
register_localhost(lower_case(_host_XMPP));
register_target(lower_case(_host_XMPP));
register_target("xmpp:"+ lower_case(_host_XMPP));
# endif
#endif
// base64decode("test2000");
// D("Digest: ->"+ make_digest("ABCDEFG", "MD5") +"<-\n");
//#ifdef PSYC_SYNCHRONIZE
// // we could generalize this into a _request_circuit_persistent
// // which tells both sides of the circuit to disable 'pushback'
// // and keep queues persistent forever
// //call_out(#'psyc_sendmsg, 1, PSYC_SYNCHRONIZE, "_request_synchronize",
// call_out(#'sendmsg, 1, PSYC_SYNCHRONIZE, "_request_synchronize",
// "I'm up and ready for some hot sync action!", ([ ]),
// SERVER_UNIFORM); //, parse_uniform(PSYC_SYNCHRONIZE));
//# echo PSYC_SYNCHRONIZE activated.
//#endif
}
// the key is intended to be a uniform, the setting a (psyc) keyword
varargs mixed config(mixed key, string setting, mixed value) {
unless (key) {
P1(("config for %O: %O -> %O from %O. map is %O.\n",
key, setting, value, previous_object(), confmap))
return 0; // no uncontrolled write access to confmap
}
if (value) {
P2(("config for %O: %O -> %O from %O. was: %O\n",
key, setting, value, previous_object(), confmap[key]))
unless (member(confmap, key)) confmap[key] = ([]);
return confmap[key][setting] = value;
}
if (setting) {
unless (member(confmap, key)) return 0;
return confmap[key][setting];
}
return copy(confmap[key]); // no uncontrolled write access
}
# ifndef __PIKE__
/*
* what this notify context stuff is about:
* we could multicast presence
* this will build the data structure necessary for this
* currently only for remote users
* for local users we dont need to use this bandwidth optimization
* but it may reduce code if we can streamline this
*
* TODO: delete local user from this context if he goes offline
*
*/
// set local object as handler for context
void set_context(object localo, mixed context) {
P3(("set %O as context for %O\n", localo, context))
PROTECT("SET_CONTEXT")
contexts[context] = localo;
}
// calling "join" is inappropriate for places, isnt it?
varargs void register_context(object localuser, mixed context, array(mixed) u) {
P3(("register %O in context %O\n", localuser, context))
PROTECT("REGISTER_CONTEXT")
unless(contexts[context]) {
contexts[context] = clone_object(NET_PATH "group/slave");
#ifdef PERSISTENT_SLAVES
/* persistent cslaves code following */
contexts[context]->load(context, u);
#endif
}
contexts[context] -> insert_member(localuser);
}
void deregister_context(object localuser, mixed context) {
PROTECT("DEREGISTER_CONTEXT")
if(objectp(contexts[context])) contexts[context] -> remove_member(localuser);
// TODO: clean up empty contexts?
}
mixed find_context(mixed context) {
P3(("searching for %O in %O\n", context, contexts))
// do we really want that qName() in here?
return contexts[objectp(context) ? context -> qName() : context ];
}
#ifdef CACHE_PRESENCE
/* this is a rather centralistic approach to presence caching
* probably it is better to do this in the respective group/slave clones
*/
varargs int persistent_presence(mixed who, int availability) {
// anything else but PROTECT() is too expensive
PROTECT("PERSISTENT_PRESENCE")
// only cache for remote people here
// we could also store a timestamp here so we know how up2date this
// information is.. for now let's simply delete the cache if the
// server restart took too long
if (availability) presence_cache[who] = availability;
// timestamps: return AVAILABILITY_UNKNOWN if the information is too old?
// it is to be proven if timestamps are useful at all, so better
// gain some experience without them, first.
return presence_cache[who];
}
#endif
#ifdef SYSTEM_SECRET
// just use the define directly..
//string server_secret_of_the_day() { return SYSTEM_SECRET; }
#else
# ifdef RANDHEXSTRING
// used by jabber's dialback thang
// in contrast, this is ultimatevily important
volatile string ssotd;
string server_secret_of_the_day() {
// if available, use sha256 for hashing please
unless (ssotd) ssotd = RANDHEXSTRING;
return ssotd;
}
# endif
#endif
mapping system_queue() {
P3(("system_queue: %O\n", systemQ))
PROTECT("SYSTEM_QUEUE")
return systemQ;
}
// create a named clone -- amylaar only
object named_clone(string file, string name) {
// string rc;
// should use return value?
P3(("named_clone %O for %O. stack: %O\n", file, name,
caller_stack()))
PROTECT("NAMED_CLONE")
P2(("legal_name(%O) for named_clone of %O\n", name, file))
unless (name = legal_name(name)) return (object)0;
file += "#" + name;
// D(name +" is to be summoned --------------------------------\n");
// rc = file -> sName(name);
// return find_object(file);
return file -> sName(name);
}
#endif //PIKE
#ifndef USE_LIVING
volatile mapping people = ([]);
void register_person(string name, object o) {
PROTECT("REGISTER_PERSON")
if (o) people[name] = o;
else m_delete(people, name);
P3(("register_person(%O, %O)\n", name, o))
}
// .... the lowercazed optimization actually doesn't
// help, there is hardly a use for it.. so it may disappear again.
varargs object find_person(string name, vaint lowercazed) {
if (!lowercazed) name = lower_case(name);
return people[name];
}
int amount_people() { return sizeof(people); }
array(object) objects_people() {
PROTECT("OBJECTS_PEOPLE")
return m_values(people);
}
#endif
//object relay;
// could get used by servers aswell, huh?
varargs object summon_person(string nick, vamixed blueprint) {
object o;
P2(("summon_person %O of %O for %O\n", nick, blueprint,
previous_object()))
PROTECT("SUMMON_PERSON")
unless (stringp(nick)) {
D(S("%O: request from %O to summon_person %O, %O\n",
ME, previous_object(), nick, blueprint));
return 0;
}
if (o = find_person(nick)) return o;
#if 0 //def RELAY_OBJECT
// unless (relay) relay = find_object(RELAY_OBJECT);
// unless (relay) return 0;
// return relay;
// return RELAY ":"+ nick;
return named_clone(NET_PATH "irc/ghost", nick);
#else
// should it be "person" ?
unless(blueprint) blueprint = DEFAULT_USER_OBJECT;
// no, because person currently doesnt work standalone
return named_clone(blueprint, nick);
#endif
}
// look up interface.h for a macro doing the same job
#ifndef is_formal
string is_formal(string nicki) {
// uniform does not check for objects, so you MUST do that
// yourself first.
//unless (stringp(nicki)) return 0;
//
// checking for colon after scheme should actually be enough
// but we also check for user@host syntax, which in the long
// term could be interpreted as "find out which scheme works"
// for this person (psyc, xmpp, mailto..)
//
# echo We don't get here anyway.
# if 1
if (index(nicki, ':') != -1 || index(nicki, '.') != -1)
return nicki;
# else
if (index(nicki, ':') != -1 || index(nicki, '@') != -1)
return nicki;
# endif
return 0;
}
#endif
#ifndef lower_uniform
// same thing, but returns lowercased name
// not useful for xmpp urls, but elsewhere sometimes
string lower_uniform(string nicki) {
if (index(nicki, ':') != -1 || index(nicki, '@') != -1)
return lower_case(nicki);
return 0;
}
#endif
void register_scheme(string scheme) {
PROTECT("REGISTER_SCHEME")
services[scheme] = schemes[scheme] = previous_object();
}
void register_service(string service) {
PROTECT("REGISTER_SERVICE")
services[service] = previous_object();
}
object find_service(string service) {
object o;
// schemes are outgoing gateways, but also
// incoming gateways are services, therefore
// newsfeeds etc need to do a register_service()
// currently we just keep them in the same mapping
// and see if thats good or bad..
if (services[service]) return services[service];
#ifndef __PIKE__
#ifdef SERVICE_PATH
else {
if (o = ((SERVICE_PATH + service) -> load()))
return services[service] = o;
}
#endif
#endif // PIKE
//return find_object("/service/"+ service);
}
#ifndef ADMINISTRATORS
#define ADMINISTRATORS
#endif
#ifndef OPERATORS
#define OPERATORS
#endif
volatile array(mixed) admins = ({ ADMINISTRATORS });
volatile array(mixed) opers = ({ OPERATORS });
// returns "percentage" of bosslihood
// currently only 0, 50 and 100 is used
//
int boss(mixed guy) {
if (objectp(guy)) {
#ifdef BOSS_HOST_IP
if (query_ip_number(guy) == BOSS_HOST_IP) return 90;
#endif
guy = guy -> qName();
}
if (stringp(guy)) guy = lower_case(guy);
if (index(opers, guy) >= 0) return 50;
if (index(admins, guy) >= 0) return 100;
return 0;
}
mixed find_place(mixed a) {
string path, err;
object o;
if (objectp(a)) return a;
if (path = lower_uniform(a)) return path;
unless (a = legal_name(a)) return 0;
path = PLACE_PATH + lower_case(a); // assumes amylaar
o = find_object(path);
if (o) return o;
// PT(("trying to load %O from %O..\n", a, path))
// return ME seitens des places wäre netter..
#ifndef __PIKE__ // TPD
err = catch(o = path -> load(a));
#endif //PIKE
D1(if (err) D("Error loading place: "+err);)
if (objectp(o)) {
ASSERT("find_place", o == find_object(path), path)
return o;
}
P1(("Warning: place %O did not return ME on load()\n", path))
return find_object(path); // auch egal
}
#ifdef PUBLIC_PLACES
// format "localname/uniform", "description", ...
volatile array(string) _places = ({ PUBLIC_PLACES });
// the name 'public_places()' confuses vim's syntax detection
// of lpc.. because 'public' is an lpc keyword.. heehee
array(string) advertised_places() { return _places; }
#endif
#if !defined(SMTP_PATH) || !defined(PRO_PATH)
string legal_mailto(string a) {
unless (stringp(a) && strlen(a) > 5) return 0;
if (index(a, '@') < 1 ||
index(a, '.') < 3 ||
index(a, ' ') >= 0) return 0;
return lower_case(a);
}
#endif
#ifndef hex2int
// thanks to saga this does now convert hex to integer.. :)
//
// modern ldmud now offers hex2int in form of
// #define hex2int(HEX) to_int("0x"+ HEX)
// i think we should provide hex2int() either
// as macro or library function depending on
// ldmud version.
int hex2int(string hex) {
int x, i, r, len = strlen(hex);
each (x, lower_case(hex)) {
unless ((x>='0' && x<='9') || (x>='a' && x<='f')) return 0;
if (x >= 'a' && x <= 'f') {
x -= 87;
} else {
x -= 48;
}
// D(S("x: %d\n", x));
r += to_int(x * pow(16, len - ++i));
}
return r;
}
#endif
#if 0
// only used by /lu these days
int greater_user(object a, object b) { return file_name(a) > file_name(b); }
object* sorted_users() { return sort_array(users(), #'greater_user; }
#endif
int xmpp_sendmsg(mixed target, string mc, mixed data, mapping vars,
mixed source, array(mixed) u, int showingLog, string otarget) {
string tmp;
object o;
// we use C:xmpp:host(:port) as an object name
// we can change that if it is confusing
//tmp = u[UCircuit] || "xmpp:"+ u[UHostPort];
// xmpp://user@host is not valid. should probably send an error
if (u[USlashes] == "//") {
P2(("xmpp uniform uslashes set, bailing out\n"))
return -1;
}
tmp = "xmpp:"+ u[UHostPort];
if (o = targets[tmp]) {
P2(("%O to be delivered on %O\n",
otarget, o ))
} else if (is_localhost(u[UHost])) {
unless (u[UUser]) {
P0(("Intercepted %O to %O from %O\n", mc, target, source))
// 0 makes sendmsg try to relay via xmpp.scheme.psyced.org
// but fippo doesn't like that
return -4;
}
// this is a lot simpler than find_psyc_object()
if (u[UUser][0] == '*' || u[UUser][0] == '#') // DASH_COMPAT
o = find_place(u[UUser][1..]);
else
o = summon_person(u[UUser]);
// we get here when net/jabber/* doesn't do this job itself
// as in fact it probably shouldn't in many cases.
} else {
#ifdef JABBER_PATH
# ifdef QUEUE_WITH_SCHEME
o = ("C:"+tmp)-> circuit(u[UHost], u[UPort]
|| JABBER_S2S_SERVICE, 0, "xmpp-server",
tmp, systemQ);
# else
o = ("C:"+tmp)-> circuit(u[UHost], u[UPort]
|| JABBER_S2S_SERVICE, 0, "xmpp-server",
u[UHostPort], systemQ);
# endif
register_target(tmp, o);
// traditional co-hosting using port
// number in uniform is not legal in jabber
if (u[UPort])
register_target("xmpp:"+ u[UHost], o);
#else
// we could as well return 0 here, then sendmsg would
// ask xmpp.scheme.psyced.org for relay services..
// but fippo doesn't like that
return source -> w("_error_unavailable_scheme",
"Scheme '[_scheme]' is not available",
([ "_scheme" : "xmpp" ]));
#endif
}
register_target(target, o);
return o->msg(source,mc,data,vars,
showingLog,otarget);
}
#ifndef FORK
// PSYC-enabled message delivery function
varargs mixed sendmsg(mixed target, string mc, mixed data, vamapping vars,
vamixed source, vaint showingLog, vaclosure callback,
varargs vamixed extra) { // proto.h!
mixed tmp;
array(mixed) u;
object o;
P3(("sendmsg(%O,%O,%O,..,%O,%O,%O)\n",
target, mc, data, source, showingLog, callback))
#ifdef SANDBOX
// we can't avoid having this check in here, so we better avoid
// having two parallel security systems for the sandbox and thus
// disallow sending psyc messages without using sendmsg()..
// this keeps the sandbox tidy and intelligible :)
//
if (extern_call() && (!geteuid(previous_object())
|| stringp(geteuid(previous_object()))
&& geteuid(previous_object())[0] != '/')) {
unless (source == previous_object()
|| source == vars["_context"]) {
raise_error(sprintf("INVALID SENDMSG by %O(%O) (pretended "
"to be target/context %O/%O\n",
previous_object(),
geteuid(previous_object()),
target,
vars["_context"]));
}
}
#endif
unless (source) source = previous_object();
// entity.c doesn't allow vars to be missing so we might
// just aswell enforce it in the whole psyced source that
// vars always need to be given as mapping. TODO
// i changed the behaviour of entity.c because vars are missing
// everywhere..
unless (mappingp(vars)) vars = ([]);
#ifdef TAGGING
/* <fippo> I dont remember exactly why I did not want this
* for stringp sources... but for pushback, it should
* execute even for stringp(source)
* <lynX> was objectp(source), changed to just 'source' to
* ensure origin hasn't been destructed in the meantime
* should we restore it in such a case?
*/
#endif
// target = lower_case(target) ist fuer xmpp nicht
// gut, weil der resource-teil dort case-sensitive
// ist... der node@domain-Part aber nicht
if (stringp(target)) {
int i;
string otarget;
#ifdef _flag_encode_uniforms_IRC
// TODO: move this to net/irc if anyone cares
// ist das nicht ein alter hack fuer net/irc?
// wieso issn der in der lib?
// alt aber immer noch aktiv... sagt fippo 2007-05-20
// 2007-12-27 problem with this when people
// use a msn transport which encodes their
// buddies as xmpp:user%host@msntransport
// this code simply does not belong here, but
// should be in irc/user::sendmsg and check
// for verbatimuniform setting
if ((i = index(target, '%')) != -1) {
target[i] = '@';
}
// vielleicht geht !verbatimuniform sonst nicht
// vielleicht weiß fippo genaueres
#endif
otarget = target;
target = lower_case(target);
#if 1
//D("sendmsg for "+target+"\n");
tmp = targets[target];
if (tmp) { // && interactive(tmp)) {
P2(("delivery agent %O for %O (%s)\n", tmp, target,
mc || "0"))
return tmp->msg(source, mc, data, vars, showingLog, otarget);
}
// no, we don't do any cleaning of the targets mapping
// because reconnecting will probably fill the slot again
//
// and this also is of no use:
//else if (tmp = find_object(target)) target = tmp;
// object names are sound and smoke.
#else
// if (member(targets, target)) {
// tmp = targets[target];
// if (tmp) return tmp -> msg(source, mc, data, vars,
// showingLog, target);
// else m_delete(targets, target);
// }
#endif
if (u = parse_uniform(target)) {
P4(("sendmsg: %O parsed as %O\n", target, u))
// previous_object()->w(
// "_error_invalid_uniform",
// "Looks like a malformed URL to me.");
// return 0;
// }
switch(u[UScheme]) {
#ifdef PSYC_PATH
case "psyc":
return psyc_sendmsg(target, mc, data, vars,
showingLog, source, u);
#endif
case 0:
#ifdef DEVELOPMENT
raise_error("scheme 0 is a bug\n");
//
// TODO: we had this error, and maybe it's
// because user@host addressing does get here
// so i'm not completely sure it is the right
// thing to do to just treat it like xmpp, but
// let's give that a try.
#endif
//
// fall thru
case "xmpp":
#ifdef SWITCH2PSYC
P4(("LOOKing for %O in %O\n",
"psyc://"+u[UHost]+"/", targets))
tmp = targets["psyc://"+u[UHost]+"/"];
if (tmp) { // && interactive(tmp)) {
PT(("SWITCH2PSYC delivery %O for %O (%s)\n", tmp, target,
mc || "0"))
// we should very probably generate a redirect here instead! TODO
return tmp->msg(source, mc, data, vars, showingLog, otarget);
}
#endif
// actually jabber does not allow other ports
// in the url.. but anyway, here
// comes a jabber implementation which does.
// so you can only use it to debug jabber
// code of other psyceds ;)
//
if (xmpp_sendmsg(target, mc, data, vars,
source, u, showingLog, otarget)) return 5;
break;
#ifdef SMTP_PATH
case "mailto":
unless (tmp = legal_mailto(u[UUserAtHost])) {
sendmsg(source, "_error_invalid_mailto",
"Sorry, that doesn't look like a valid email address to me.");
return 0;
}
o = summon_person(tmp, SMTP_PATH "user");
unless (o) return 0;
register_target(target, o);
o -> msg(source, mc, data, vars);
return 3;
#endif
}
if (schemes[u[UScheme]])
return schemes[u[UScheme]]->msg(source,
mc, data, vars, showingLog, target);
// ifdeffing out isnt the smartest of options, but for now..
#if defined(GATEWAY_RELAYING) && !defined(BRAIN)
// also, gateway discovery works but gateway relaying on the receiving
// side isn't. additionally, the other psyc boys don't like this approach.
if (u[UScheme]
# if __EFUN_DEFINED__(regmatch)
&&! regmatch(u[UScheme], "[^a-z0-9]")
# else
&&! sizeof(regexp( ({ u[UScheme] }), "[^a-z0-9]"))
# endif
) {
// current gateway discovery strategy
u = parse_uniform("psyc://"+ u[UScheme] +
".scheme.psyced.org/$"+ u[UScheme] +
"/"+ u[UBody]);
return psyc_sendmsg(target, mc, data, vars,
showingLog, source, u);
}
#endif
sendmsg(source, "_error_unknown_scheme",
"'[_scheme]' in '[_source_invalid]' is not a supported protocol scheme.",
([ "_scheme": u[UScheme], "_source_invalid": target ]));
return 0;
}
#if defined(PSYC_PATH) && defined(JABBER_PATH) && !defined(RELAY)
else if (index(target, '@') != -1 || index(target, '.') != -1) {
string host;
// simple user@host notation.
// this is probably a jabber client talking.
//
// first check for special jid2psyc syntaxes
switch(target[0]) {
case '#': // DASH_COMPAT
sscanf(target, "#%s@%s", tmp, host);
case '*':
sscanf(target, "*%s@%s", tmp, host);
tmp = "psyc://" + host + "/@" + tmp;
return psyc_sendmsg(tmp, mc, data, vars,
showingLog, source, parse_uniform(tmp));
case '^':
target[0] = '~';
case '~':
case '$':
sscanf(target, "%s@%s", tmp, host);
tmp = "psyc://" + host + "/" + tmp;
return psyc_sendmsg(tmp, mc, data, vars,
showingLog, source, parse_uniform(tmp));
}
// we pretend target is xmpp: to figure out
// which object to incarnate
// should we check targets[] again!??
u = parse_uniform("xmpp:"+ target);
//target = "xmpp:"+ target;
//u = parse_uniform(target);
// but we leave the target as u@h
// in case xmpp manages to switch to psyc
// .. then again, not necessary as we defined a
// psyc user to be the same as an xmpp user
// whenever a switch is possible
// ok, but later targets will deliver the u@h without
// xmpp: to jabber anyway, so mkjid() needs to be able
// to deal with that
// TODO: if xmpp-s2s is not active, this should
// probably assume that this is a psyc address
if (xmpp_sendmsg(target, mc, data, vars, source, u,
showingLog, otarget)) return 6;
}
#endif
#ifdef RELAY
// we get here for some funny reasons when doing /nick.. TODO
P1(("CAUGHT sendmsg(%O,%O,%O,%O,%O,%O,%O)\n",
target, mc, data, vars, source, showingLog, callback))
#else
// we could even start looking for local nicknames here..
// but this would circumvent /alias
sendmsg(source, "_error_unknown_name_user",
"[_nick_target] isn't available.",
([ "_nick_target" : target ]));
#endif
return 0;
}
if (objectp(target)) {
target -> msg(source, mc, data, vars, showingLog);
// make sure msg is treated as successfully delivered:
return 2;
}
D2(else D(S("sendmsg encountered %O as target for (%O,%s,%O,%O)\n",
target, source, mc, data, vars));)
return 0;
}
#else // FORK {{{
volatile object _psyced;
#define PSYCED (_psyced || _psyced = DAEMON_PATH "psyc" -> load(), _psyced)
object cookie_factory(string uni) { // uni must be lowercase
mixed *u;
string tmp;
object o;
if (o = targets[uni]) return o;
u = parse_uniform(uni);
unless (u) return 0;
switch(u[UScheme]) {
#ifdef PSYC_PATH
case "psyc":
if (o = targets[u[URoot]]) {
P2(("%O to be delivered on %O's circuit\n",
uni, query_ip_name(o) || o ))
register_target(uni, o);
// was delivermsg(
return o;
// no more cleansing of targets mapping.. see also net/library.i
// } else {
// P1(("PSYC/TCP target %O gone!\n", t2))
// m_delete( targets, t2 );
// }
}
return PSYCED;;; // three semicolons because of extra importance
//return psyc_sendmsg(target,mc,data,vars,showingLog,source,u);
#endif
#ifdef JABBER_PATH
case "xmpp":
// actually jabber does not allow other ports
// in the url.. but anyway, here
// comes a jabber implementation which does.
// so you can only use it to debug jabber
// code of other psyceds ;)
//
//tmp = u[UCircuit] || "xmpp:"+ u[UHostPort];
tmp = "xmpp:"+ u[UHostPort];
if (o = targets[tmp]) {
P2(("%O to be delivered on %O\n", uni, o ))
} else {
// we use C:xmpp:host(:port) as an object name
// we can change that if it is confusing
#ifdef QUEUE_WITH_SCHEME
o = ("C:"+tmp)-> circuit(u[UHost], u[UPort]
|| JABBER_S2S_SERVICE, 0, "xmpp-server",
tmp, systemQ);
#else
o = ("C:"+tmp)-> circuit(u[UHost], u[UPort]
|| JABBER_S2S_SERVICE, 0, "xmpp-server",
u[UHostPort], systemQ);
#endif
register_target(tmp, o);
// jabber scheme makes it impossible to
// cohost several servers on one ip#
if (u[UPort])
register_target("xmpp:"+ u[UHost], o);
}
register_target(uni, o);
return o;
//return o->msg(source,mc,data,vars, showingLog,otarget);
#else
/*
case "xmpp":
return source -> w("_error_unavailable_scheme",
"Scheme '[_scheme]' is not available",
([ "_scheme" : "xmpp" ]));
*/
#endif
#ifdef SMTP_PATH
case "mailto":
unless (tmp = legal_mailto(u[UUserAtHost])) {
#if 0
source ->w(
"_error_invalid_mailto",
"Sorry, that doesn't look like a valid email address to me.");
#endif
return 0;
}
o = summon_person(tmp, SMTP_PATH "user");
unless (o) return 0;
register_target(uni, o);
return o;
//o -> msg(source, mc, data, vars);
#endif
}
if (schemes[u[UScheme]])
return schemes[u[UScheme]];
//return schemes[u[UScheme]]->msg(source, mc, data, vars, showingLog, target);
// ifdeffing out isnt the smartest of options, but for now..
#ifndef BRAIN
if (u[UScheme]
# if __EFUN_DEFINED__(regmatch)
&&! regmatch(u[UScheme], "[^a-z0-9]")
# else
&&! sizeof(regexp( ({ u[UScheme] }), "[^a-z0-9]"))
# endif
) {
// current gateway discovery strategy
u = parse_uniform("psyc://"+ u[UScheme] +
".scheme.psyced.org/$"+ u[UScheme] +
"/"+ u[UBody]);
return PSYCED;
//return psyc_sendmsg(target,mc,data,vars,showingLog,source,u);
}
#endif
// TODO
#if 0
source ->pr("_error_unknown_scheme",
"%O is not a supported protocol scheme.\n",
u[UScheme]);
#endif
return 0;
}
varargs int sendmsg(mixed target, string mc, mixed data, mapping vars,
mixed source, int showingLog) { // proto.h!
mixed tmp, *u;
object o;
P3(("sendmsg(%O,%O,%O,..,%O,%O)\n",
target, mc, data, source, showingLog))
#ifdef SANDBOX
// we can't avoid having this check in here, so we better avoid
// having two parallel security systems for the sandbox and thus
// disallow sending psyc messages without using sendmsg()..
// this keeps the sandbox tidy and intelligible :)
//
if (extern_call() && (!geteuid(previous_object())
|| stringp(geteuid(previous_object()))
&& geteuid(previous_object())[0] != '/')) {
unless (source == previous_object()
|| source == vars["_context"]) {
raise_error(sprintf("INVALID SENDMSG by %O(%O) (pretended "
"to be target/context %O/%O\n",
previous_object(),
geteuid(previous_object()),
target,
vars["_context"]));
}
}
#endif
unless (source) source = previous_object();
#ifndef GAMMA
// entity.c doesn't allow vars to be missing so we might
// just aswell enforce it in the whole psyced source that
// vars always need to be given as mapping. TODO
unless (mappingp(vars)) vars = ([]);
#endif
// target = lower_case(target) ist fuer xmpp nicht
// gut, weil der resource-teil dort case-sensitive
// ist... der node@domain-Part aber nicht
if (stringp(target)) {
int i;
string otarget;
// ist das nicht ein alter hack fuer net/irc?
// wieso issn der in der lib?
if ((i = index(target, '%')) != -1) {
target[i] = '@';
}
otarget = target;
target = lower_case(target);
//D("sendmsg for "+target+"\n");
tmp = cookie_factory(target);
if (tmp) { // && interactive(tmp)) {
PT(("delivery agent %O for %O (%s)\n", tmp, target,
mc || "0"))
return tmp->msg(source, mc, data, vars, showingLog, otarget);
}
// TODO ERROR
}
if (objectp(target)) {
// returnwert sagt aus, ob msg dargestellt werden will
// nicht aber, dass sie erfolgreich angekommen ist, denn das ist ja eh
target -> msg(source, mc, data, vars, showingLog);
// deshalb machen wir das lieber selber klar
return 2;
}
D2(else D(S("sendmsg encountered %O as target for (%O,%s,%O,%O)\n",
target, source, mc, data, vars));)
return 0;
}
#endif // FORK }}}
#ifndef __PIKE__
#ifdef PSYC_PATH
# ifdef DRIVER_HAS_BROKEN_INCLUDE
# include "/net/psyc/library.i"
# else
# include PSYC_PATH "library.i"
# endif
#endif
#ifdef IRC_PATH
# ifdef DRIVER_HAS_BROKEN_INCLUDE
# include "/net/irc/library.i"
# else
# include IRC_PATH "library.i"
# endif
#endif
string decode_embedded_charset(string text) {
int i;
string work;
array(string) exploded1;
// we only support the system charset currently .. iconv to follow TODO
#if SYSTEM_CHARSET == "UTF-8"
exploded1 = regexplode(text, "=\\?[uU][tT][fF]-8\\?Q\\?[^?]*\\?=");
#else
exploded1 = regexplode(text, "=\\?[iI][sS][oO]-8859-15?\\?Q\\?[^?]*\\?=");
#endif
text = "";
each (work, exploded1) {
string *exploded2;
string s;
int j = 0;
if (++i % 2) {
unless (work == " ") text += work;
continue;
}
exploded2 = regexplode(replace(slice_from_end(work, 15, 3), "_", " "),
"=[A-Z0-9][A-Z0-9]");
work = "";
each (s, exploded2) {
if (++j % 2) {
work += s;
continue;
}
s = s[1..];
work += sprintf("%c", hex2int(s));
}
text += work;
}
return text;
}
#endif // __PIKE__
object library_object() {
return ME;
}