mirror of
https://github.com/ChronosX88/psyced.git
synced 2024-11-13 22:11:02 +00:00
1193 lines
36 KiB
OpenEdge ABL
1193 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();
|
|
// 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;
|
|
}
|