psyced/world/net/psyc/parse.i

1192 lines
38 KiB
OpenEdge ABL

// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: parse.i,v 1.358 2008/12/27 00:42:04 lynx Exp $
//
#ifndef FORK
#ifndef __PIKE__
# include <tls.h>
#endif
// PSYC MESSAGE PARSER - parses both MMP and PSYC the old way
//
// THIS IS THE ORIGINAL LYNXISH PSYC PARSER
// this flag should enable forward-checks of dns resolutions..
// currently we don't have that, so this flag actually disables
// use of unprooven resolved hostnames and reduces everything to
// ip numbers.
//#define HOST_CHECKS
// just the plain ip number of the remote host
// ^^ glatte lüge!
volatile string peerhost;
#if 0 //defined(PSYC_TCP) && __EFUN_DEFINED__(strrstr)
volatile string peerdomain;
#endif
// remote port number
volatile int peerport;
// unresolved-ip or ip:port
volatile string peeraddr;
// holds last ip we were connected to
volatile string peerip;
#ifdef SYSTEM_SECRET
// how much can we trust the content of this packet?
volatile int ctrust;
volatile string checkpack;
volatile mixed vcheck;
#endif
// current variables (":"), permanent variables ("=", "+", "-")
//
volatile mapping cvars = 0, pvars = ([ "_INTERNAL_origin" : ME ]);
//
// a distinction between mmp and psyc-vars should be made
// current method and incoming buffer
volatile string mc, buffer;
#ifdef REPATCHING
// cache of patched remote uniforms
volatile mapping patches = ([]);
#endif
// parsing helpers..
volatile string lastvar, mod, origin_unl;
//
// list parsing helpers..
volatile array(mixed) list;
volatile mapping hash;
volatile int l = 0;
volatile int pongtime, routing = 1;
#ifndef PSYC_TCP
// resolved UNL of remote server (psyc://hostname or psyc://hostname:port)
volatile string netloc;
# define QUIT return 1; // udp only definition
#endif
// REPATCHING! (temporarily called ifdef SCHWAN)
// we have two alternate ways to deal with unknown incoming connections
// the default one is to double check the hostname and return an error
// if the name isn't valid. the second one is to repatch unknown hosts
// into their ip numbers. the latter we obviously normally don't use
#ifndef REPATCHING
// prototype definition for #'getdata
protected int deliver(mixed ip, string host, string mc, string buffer, mapping cvars);
#endif
vamixed getdata(string a);
#ifdef __LDMUD__
// i think once upon a time the '&& vname' at the end wasn't necessary..
# define SCANFIT sscanf(a, "%1.1s%s%t%s", mod, vname, vvalue) && vname
#else
# define SCANFIT sscanf(a, "%1s%s%*[ \t]%s", mod, vname, vvalue) && vname
#endif
#define SPLITVAL(val) \
unless (sscanf(val, "%s %s", val, vdata)) vdata = 0
// sollte es wirklich ein space sein? hmmm.. %t ist sowieso zu grob..
// i would like to revert to a parser that does not need to merge
// mappings at the end of each packet, just because someone
// may think a '=' does not affect a ':'. the mmp spec will
// clarify. it's not a reason to mess up a parser for!
// the simplicity of the parsers is an objective worth
// making some things clearer in the spec rather than here!
#ifdef DONT_MERGE
# if __EFUN_DEFINED__(copy)
# define INITCVARS cvars = copy(pvars);
# else
# define INITCVARS cvars = ([ ]) + pvars;
# endif
#else
// iff we stick to this ugly variant, we can at least simplify some operations
// which are being done on both pvars and cvars.. TODO
# define MERGEVARS cvars = pvars + cvars;
#endif
// el explains:
// because in case we do a cvars = copy(pvars) in
// restart() operations on _state go wrong. imagine
// someone sends this packet:
//
// :_source psyc://someone
// =_source
//
// this would result in _source beeing empty, which is
// wrong. or do we enforce everyone to send permanent
// changes before a _set? or do we even prohibit sending
// the same var with different modifiers ?
// <lynX> yes, permanent changes need to come before the :
// the other way round is just begging to be misunderstood
// state inspector for 20after4
mixed qState(string vname) { return pvars[vname]; }
int restart() {
// delete temporary variables for next msg
cvars = ([ ]);
// delete other stuff too
buffer = "";
mc = 0;
routing = 1; // unused as yet
#ifdef SYSTEM_SECRET
checkpack = vcheck = 0;
ctrust = trustworthy;
#endif
return 1;
}
private int listAppend(string vname, string vvalue) {
string vdata;
unless (vname) {
croak("_error_syntax_list_append",
"Invalid list append without preceding variable assignment.");
QUIT
}
unless (list) {
// eek. such an array has a size of ~35 kb right after
// allocation. we should not do that!
list = allocate(PSYC_LIST_SIZE_LIMIT);
ASSERT("listAppend", stringp(cvars[vname]), buffer)
SPLITVAL(cvars[vname]);
list[l = 0] = cvars[vname];
if (vdata) hash = ([ cvars[vname] : vdata ]);
}
SPLITVAL(vvalue);
if (hash) hash[vvalue] = vdata;
else if (vdata) {
// should this ever happen?
hash = mkmapping(list, allocate(sizeof(list)));
hash[vvalue] = vdata;
}
list[++l] = vvalue;
return 1;
}
private int conclude() {
P3(("pp::conclude lastvar:%O l:%O hash:%O\n", lastvar, l, hash))
unless (lastvar) return 0;
// if (abbrev("_INTERNAL", lastvar)) not necessary because
// the following check only allows lowercase for now
if (
#ifdef GAMMA
!legal_keyword(lastvar)
#else
// very relaxed strategy
strlen(lastvar) < 2 || lastvar[0] != '_' // policy may change
|| lastvar[1] > 'z' || lastvar[1] < 'a'
#endif
) {
croak("_error_illegal_protocol_variable",
"You are not allowed to present a variable named [_variable_name].",
([ "_variable_name": lastvar ]));
QUIT
}
if (l) {
// alright, we have a list to close.. was: listEnd()
if (mappingp(hash)) {
cvars[lastvar] = hash;
hash = 0;
} else
cvars[lastvar] = list[0..l];
list = 0;
l = 0;
P2(("%O concluded %O (%O)\n", ME, lastvar, cvars[lastvar]))
//
// at this point we would need to know if the mod was =
// to also store the list in pvars, but since i'm generally
// doubtful if this is the right way to go, i'll leave it to
// a later day.. TODO
// hooray, we get to check some variable families for sanity
} else if (abbrev("_degree", lastvar)) {
// allow for unset degree '-'
if (!stringp(cvars[lastvar]) || (cvars[lastvar] != "-" &&
!sscanf(cvars[lastvar], "%1d", cvars[lastvar]))) {
P1(("%O failed to parse %O: %O\n", ME,
lastvar, cvars[lastvar]))
m_delete(cvars, lastvar);
if (mod != ":") m_delete(pvars, lastvar);
}
else if (mod != ":") pvars[lastvar] = cvars[lastvar];
} else if (abbrev("_list", lastvar)) { // _tab
// we only get here if the _list has one or zero members
#ifdef PARANOID
unless (stringp(cvars[lastvar])) {
PT(("%O wrrrooonnggg attempt to fix %O (%O)\n", ME,
lastvar, cvars[lastvar]))
} else
#endif
if (strlen(cvars[lastvar]))
cvars[lastvar] = ({ cvars[lastvar] });
else
cvars[lastvar] = ({ });
P2(("%O incoming %O (%O)\n", ME, lastvar, cvars[lastvar]))
// an empty variable is an empty variable, not zero
// } else unless (strlen(cvars[lastvar])) {
// cvars[lastvar] = 0;
}
P3(("pp::conclude cvars:%O\n", cvars))
lastvar = 0;
return 0;
}
varargs void diminish(string vname, vastring vvalue) {
string vdata; // dummy
// D4(D("diminish("+vname+","+vvalue+")\n");)
if (!vname || vname=="") vname = lastvar;
else {
conclude();
lastvar = vname;
}
if (!member(pvars, vname)) {
D4( D("diminish: no such variable '"+vname+"'\n"); )
return 0;
}
if (vvalue && vvalue != "") {
SPLITVAL(vvalue);
if (pvars[vname] == vvalue) {
m_delete(pvars, vname);
m_delete(cvars, vname);
} else if (mappingp(pvars[vname])) {
m_delete(pvars[vname], vvalue);
// should be same mapping anyway
m_delete(cvars[vname], vvalue);
} else {
log_file("PSYC", "%O DIMINISH %O from %O\n",
query_ip_name(), vvalue, pvars[vname]);
// this is critical enough to let our counterpart know
croak("_failure_unsupported_modifier_diminish",
"Sorry, can't diminish that.");
}
} else {
if (member(pvars, vname)) {
m_delete(pvars, vname);
m_delete(cvars, vname);
} else {
// javapsyc currently sends diminish twice, bug..
D3( D("diminish: no such variable '"+vname+"'\n"); )
// D3 means we don't want to know about it ;-)
}
}
}
vamixed parse(string a) {
int i;
string vname, vdata;
mixed vvalue;
#ifdef _flag_log_sockets_PSYC
log_file("RAW_PSYC", "» %O\t%s\n", ME, a);
#endif
// P3(("CVARS (%O):\n %O, %O\n", a, query_ip_number(ME), ME))
D3( if (a && strlen(a) > 2) D(S("pp:parse %O\n", a)); )
#ifdef SYSTEM_SECRET
if (checkpack) checkpack += a + "\n";
#endif
// if (a == ".") {
// PT((">>>>> RESTARTING because of .. strange '.\\n'\n"))
// restart();
// }
// else if (a[<1] == '\\') {
// buffer += a[..<2];
// D1( D("psyc-parse-multiline: "+buffer+"\n"); )
// }
// else
if (!a || a == "") {
// at this point we move on from mmp_parse to psyc_parse.
// <lynX> with bkstate coming our way we will soon have
// to put the mmp variable state away somewhere
// and go fetch the psyc variable state for the current
// source/target pair from somewhere else. TODO
routing = 0; // unused as yet
} else switch(a[0]) {
case ':':
unless (SCANFIT) {
// compatible to new syntax
if (vname && strlen(vname)) {
m_delete(cvars, vname);
lastvar = 0;
} else {
P1(("PSYC got %O from %O\n", a, query_ip_number()))
croak("_error_syntax_set",
"Couldn't parse that variable setting.");
QUIT
}
}
if (vname != "") {
conclude();
#ifdef GAMMA
// intermediate hack in lack of real type support
// which needs to be done in net/spyc
if (abbrev("_time", vname)) vvalue = to_int(vvalue);
// unused as yet: else if (abbrev("_date", vname))
// vvalue = PSYC_EPOCH + to_int(vvalue);
#endif
cvars[lastvar = vname] = vvalue;
#ifdef SYSTEM_SECRET
unless (vcheck) {
// ignore checksum if origin is trustworthy
// already.. function only available in ':'
if (trustworthy<8 && abbrev("_check", vname)) {
vcheck = vname;
checkpack = "";
// pvars invalid in checked packets
// huh? das ist doch quatsch!
//cvars = ([]);
// _INTERNAL_origin will be undefined!
} else vcheck = -1;
// only first variable setting may set checksum
}
#endif
} else listAppend(lastvar, vvalue);
break;
case '=':
// currently psyced cannot handle "=_target" (to clear a var)
// it requires a whitespace after that (like a \t char).. TODO
unless (SCANFIT) {
croak("_error_syntax_assign",
"Couldn't parse that variable assignment.");
QUIT
}
if (vname != "") { // // ensure varname is legal?
conclude();
lastvar = vname;
pvars[vname] = vvalue;
#ifdef DONT_MERGE
cvars[vname] = vvalue;
#endif
} else listAppend(lastvar, vvalue);
break;
case '+':
unless (SCANFIT) {
croak("_error_syntax_augment",
"Couldn't parse that variable addition.");
QUIT
}
if (vname != "") {
conclude();
lastvar = vname;
} else vname = lastvar;
unless (mappingp(pvars[vname])) {
SPLITVAL(pvars[vname]);
pvars[vname] = ([ pvars[vname] : vdata ]);
}
SPLITVAL(vvalue);
pvars[vname][vvalue] = vdata;
#ifdef DONT_MERGE
cvars[vname] = pvars[vname];
#endif
break;
case '-':
if (SCANFIT)
diminish(vname, vvalue);
else
diminish(a[1..]);
break;
case '?':
// should generate a query message of it own?
D2( D(a+" from PSYC/TCP ignored..\n"); )
log_file("PSYC", "%O QUERY %O\n", query_ip_name(), a);
break;
case '\t':
case ' ': // var continuation
// tab is preferred, but space is also allowed
vvalue = a[1..];
P4(("continuation.. %s %O %O\n", mod, lastvar, vvalue))
if (mod == "-") {
// croak("_error_syntax_diminish_continued",
// "Line continuation not defined for diminish.");
// QUIT
log_file("PSYC",
"%O SEMANTIX _error_syntax_diminish_continued\n", query_ip_name());
// since diminish of lists only uses first word
// as list key, anything that follows must be junk
// so we throw it away
break;
}
#ifdef DONT_MERGE
cvars[lastvar] += "\n" + vvalue;
if (mod != ":") pvars[lastvar] = cvars[lastvar];
#else
if (mod == ":") cvars[lastvar] += "\n" + vvalue;
else pvars[lastvar] += "\n" + vvalue;
#endif
break;
case '.':
// empty packet, at least without mc..
// since we dont distinguish between mmp and psyc vars here..
conclude();
if (sizeof(cvars) == 0) {
#ifndef __PIKE__
if (peerip && pongtime + 120 < time()) {
if (same_host(SERVER_HOST, peerip)) {
P1(("why am i talking psyc to myself?\n"))
} else {
#ifdef PSYC_TCP
P2(("%O sending TCP PONG to %O=%O\n",
ME, peerip, peerhost))
emit(".\n");
#else
P1(("%O sending UDP PONG to %O=%O\n",
ME, peerip, peerhost))
send_udp(peerip, peerport, ".\n");
#endif
}
pongtime = time();
}
#endif
} else if (!member(cvars, "_source")) {
// this is a change of state.
MERGEVARS // until we have proper array support..
rootMsg(0, 0, 0, cvars);
// why do we deliver an empty message anyway? shouldn't
// we deliver the + - and = events instead? i mean..
// figuring out which vars have changed is so much hairier!?
}
restart();
break;
case '_':
// default:
conclude();
if (cvars["_length"]) {
croak("_failure_unsupported_module_length",
"Sorry, cannot deal with _length yet.");
QUIT
}
MERGEVARS
#ifdef BITKOENIG_SYNTAX
sscanf(a, "%s %s", a, buffer);
#endif
#ifdef GAMMA
if (!legal_keyword(a))
#else
// pretty inefficient strategy here
for (i=strlen(a)-1; i>=0; i--)
unless (a[i] == '_' ||
(a[i] >= 'a' && a[i] <= 'z') ||
(a[i] >= '0' && a[i] <= '9') ||
(a[i] >= 'A' && a[i] <= 'Z'))
#endif
{
croak("_error_illegal_method",
"That's not a valid method name.");
QUIT
}
mc = a;
#ifdef PSYC_TCP
if (abbrev("_data", mc)) {
croak("_failure_unsupported_method_data",
"Sorry, cannot deal with binary data yet.");
QUIT
}
next_input_to(#'getdata);
#endif
conclude();
P3(("»pp:method(m:%O, v:%O)\n", mc, cvars))
switch(mc) {
// okay.. this change needs more preparation.. psyced aint ready for this :(
// sorry ppl -lnyX
// case "_message_public":
// case "_message_private":
case "_conversation": // we will soon have to decide its final name...
case "_converse":
case "_talk":
mc = "_message";
break;
case "_notice_circuit_established":
unless (origin_unl) origin_unl = cvars["_source"];
break;
case "_request_circuit_shutdown":
#ifdef PSYC_TCP
PT(("%O got %s <- selfdestructing.\n", ME, mc))
destruct(ME);
#else
P0(("%O got %s, but we HAVE no circuit!\n", ME, mc))
#endif
return 1;
}
return 1;
default:
log_file("PSYC", "%O SYNTAX %O\n", query_ip_name(), a);
croak("_error_invalid_method_compact",
"Compact methods undefined as yet.");
QUIT
}
#ifdef PSYC_TCP
next_input_to(#'parse);
if (flags & TCP_PENDING_TIMEOUT) {
remove_call_out(#'quit);
flags -= TCP_PENDING_TIMEOUT;
}
#endif
#ifndef __PIKE__
return 1;
#endif
}
#ifdef REPATCHING // {{{
private repatch(string t) {
//if (patches[t]) {
if (0) {
D3(D("psyc:parse * repatched "+t+" to "+patches[t]+"\n");)
} else {
array(mixed) u = parse_uniform(t);
#if 0 //defined(PSYC_TCP) && __EFUN_DEFINED__(strrstr)
unless (peerdomain) {
// if (abbrev("ve.", peerhost)) {
int x = strrstr(peerhost, ".");
if (x>=0) x = strrstr(peerhost, ".", -x);
if (x>=0) peerdomain = peerhost[x..];
// }
else peerdomain = "X";
P3(("peerdomain %O of %O\n",
peerdomain, peerhost))
}
#endif
// wir vertrauen der port nummer!
P3(("···· UHost=%O peerhost=%O URoot=%O\n",
u[UHost], peerhost, u[URoot]))
if (u[UHost] != peerhost
&& !same_host(u[UHost], peerhost)
#if 0 //defined(PSYC_TCP) && __EFUN_DEFINED__(strrstr)
&& !trail(peerdomain, u[UHost])
#endif
&& find_target_handler(u[URoot]) != ME) {
u[UHost] = peerhost;
patches[t] = render_uniform(u);
D2(D("psyc:parse * patched "+t+" to "+patches[t]+"\n");)
} else {
P3(("psyc:parse * "+t+" not patched\n"))
patches[t] = t;
}
register_target(patches[t]);
}
P3(("repatch: %O -> %O = %O\n", ME, peerhost, patches[t]))
return patches[t];
}
#endif // }}}
vamixed getdata(string a) {
#ifdef _flag_log_sockets_PSYC
log_file("RAW_PSYC", "» %O\t%s\n", ME, a);
#endif
if (a != ".") {
#ifdef SYSTEM_SECRET
if (checkpack) checkpack += a + "\n";
#endif
if (buffer != "") buffer += "\n";
buffer += a;
#ifdef PSYC_TCP
next_input_to(#'getdata);
#endif
} else {
#ifndef REPATCHING
array(mixed) u;
string t = cvars["_context"] || cvars["_source"];
# ifdef PSYC_TCP
// let's do this before we deliver in case we run into
// a runtime error (but it's still better to fix it!)
next_input_to(#'parse);
# endif
if (!t || trustworthy > 5) {
deliver(0, 0, mc, buffer, cvars);
} else unless (u = parse_uniform(t)) {
croak("_error_invalid_uniform",
"Looks like a malformed URL to me.");
QUIT
// henner suggests this shouldn't be a QUIT.. TODO
#ifdef __PIKE__
}
deliver(0, 0, mc, buffer, cvars);
#else
# ifdef PSYC_TCP
// Authenticated
} else if (qAuthenticated(NAMEPREP(u[UHost]))) {
if (u[UTransport] && (u[UTransport] !=
# if __EFUN_DEFINED__(tls_query_connection_state)
tls_query_connection_state() ? "s" :
# endif
"c")) {
P1(("%O sends me wrong transport %O in %O\n",
ME, u[UTransport], t))
croak("_error_invalid_uniform_transport",
"[_uniform] promotes wrong transport '[_transport]'.",
([ "_uniform": t,
"_transport": u[UTransport] ]));
// QUIT
} else {
P2(("%O has accepted %O by qAuthenticated auth\n", ME, t))
deliver(0, 0, mc, buffer, cvars);
}
# if __EFUN_DEFINED__(tls_query_connection_state)
} else if (tls_query_connection_state()) {
# ifdef _flag_report_bogus_certificates
monitor_report("_warning_invalid_host_certificate",
S("%O presented an uncertified host %O.",
ME, u[UHost]));
# else
P1(("%O presented an uncertified host %O.",
ME, u[UHost]))
# endif
# ifdef _flag_log_bogus_certificates
log_file("CERTS", S("%O %O %O\n",
ME, u[UHost], tls_certificate(ME, 0)));
# endif
// vs. _flag_reject_bogus_certificates huh?
# ifndef _flag_allow_invalid_host_certificate
croak("_error_invalid_host_certificate",
"[_host] is not an authenticated source host.",
([ "_host": u[UHost] ]));
QUIT
# endif
# endif
# endif
// fippo suggests we should have a protocol that enables
// hostnames on the other side first, before we deliver data
// to it. currently this could be abused for a DoS attack.
//
// in the case of udp without udp circuits, such an exchange
// doesn't make sense: udp psyc is intended to be lightweight.
// in that case we shouldn't resolve here, we should simply
// reject the source information, or repatch it. the fact that
// we are receiving messages is still useful. better even if
// we had a cryptographic way to protect udp packets, making
// even potential ip spoofing irrelevant. -lynX
//
} else dns_resolve(u[UHost], #'deliver,
u[UHost], mc, buffer, cvars);
#endif
restart();
}
return 1;
}
// we are in ifndef REPATCHING
// this additional function to handle name resolution does not
// exist in REPATCHING mode. see explanation at the top of the file.
protected int deliver(mixed ip, string host, string mc, string buffer, mapping cvars) {
string psycaddr;
# if 0 //def __PIKE__
// temporary!
return doneParse(ip, host, mc, buffer, cvars);
# else
// PT(("deliver(%O, %O, %O, %O, %O)\n", ip, host, mc, buffer, cvars))
// when erq isn't running we get ip == host .. maybe return -1 instead?
// and why return -1 for failed dns when we could use 0 ? FIXME
// no wait.. why is ip==0 being let thru in this code? did i forget
// about something?
//# ifdef PSYC_TCP
if (ip) {
//# endif
if (ip == -1) {
monitor_report("_warning_invalid_host",
S("%O could not resolve %O.", ME, host));
croak("_error_invalid_host", "Could not resolve [_host].",
([ "_host": host ]));
QUIT
return 1;
} else if (ip == host) {
// Happens when 'erq' could not be spawned. We allow
// the message through anyway (thus spoofable) and
// just complain about it.
monitor_report("_warning_unavailable_resolution",
S("%O could not resolve %O. Good luck!", ME, host));
// This is a clear case of a _failure, but if you insist
// on running your psyced without dns resolution, who are
// we to intentionally break your experience of unsafety?
} else if (ip == peerip) {
//PT(("deliver: from %O\n", peerhost))
# ifdef STRICT_DNS_POLICY
if (peerhost == peerip) {
P0(("deliver: got %O from %O but host %O is not resolved yet.\n", mc, ip, peerhost))
croak("_error_invalid_host_slow",
"Could not resolve [_host] quickly enough for you!",
([ "_host": peerip ]));
QUIT
//return 1;
}
# endif
//# ifdef PSYC_TCP
} else {
monitor_report("_warning_rejected_relay_incoming",
"Dropped a packet from "+ peerip +
" trying to relay for "+ host +" ("+ ip +")");
P3(("Dropped a packet from "+ peerip +
" trying to relay for "+ host +" ("+ ip +")"+"\n"+
" in parse:deliver(%O, %O, %O, %O, %O)\n",
ip, host, mc, buffer, cvars))
croak("_error_rejected_relay_incoming",
"Your address [_host_IP] is not permitted to send as [_host].",
([ "_host": host, "_host_IP": peerip ]));
QUIT
return 1;
}
//# endif
}
# ifdef PSYC_TCP
else if (psycaddr = cvars["_context"] || cvars["_source"]) {
register_target(lower_case(psycaddr));
}
# endif
# endif // PIKE
{ // looks strange but is correct
#endif // !REPATCHING
mixed source, context, t, t2;
object o;
int i;
P3(("parsed: (%O,%O,%O from %O)\n", mc, buffer, cvars, pvars))
#ifndef __PIKE__
#ifdef PSYC_TCP
if (host) sAuthenticated(host); // dns resolution just once
#endif
#ifdef SYSTEM_SECRET
if (checkpack) {
// TODO: extend logic to allow for per-host secrets
// for example using the /config command..
switch (vcheck) {
#if __EFUN_DEFINED__(hmac)
# ifndef DONT_TRUST_SHA224
case "_check_SHA224":
break;
# endif
# ifndef DONT_TRUST_SHA256
case "_check_SHA256":
ctrust = (hmac(TLS_HASH_SHA256,
// TODO: hash(TLS_HASH_SHA256, SYSTEM_SECRET) only needs to be computed once
// for the entire system, and stored in the library.
hash(TLS_HASH_SHA256, SYSTEM_SECRET),
checkpack) == lower_case(cvars[vcheck])) ? 8: 0;
break;
# endif
# ifndef DONT_TRUST_SHA512
case "_check_SHA512":
break;
# endif
#endif
// should move out of #ifdef SYSTEM_SECRET if
// anyone actually likes to use this
case "_check_integrity_SHA1":
if (sha1(checkpack) !=
lower_case(cvars[vcheck])) {
croak("_error_invalid_integrity",
"Strange, integrity check [_integrity] failed.",
([ "_integrity": cvars[vcheck] ]) );
return 1;
}
break;
#ifndef DONT_TRUST_SHA1
case "_check_SHA1":
ctrust = (sha1(SYSTEM_SECRET + checkpack) ==
lower_case(cvars[vcheck])) ? 8 : 0;
break;
#endif
case "_check_fake":
ctrust = (md5(SYSTEM_SECRET) ==
lower_case(cvars[vcheck])) ? 8 : 0;
break;
case "_check":
#ifndef DONT_TRUST_MD5
case "_check_MD5":
ctrust = (md5(SYSTEM_SECRET + checkpack) ==
lower_case(cvars[vcheck])) ? 8 : 0;
//PT(("checksum %O == %O for %O + %O results in %O\n", md5(SYSTEM_SECRET + checkpack), cvars, SYSTEM_SECRET, checkpack, ctrust))
break;
#endif
default:
// _warn_unsupported_check_method ?
// ctrust remains at host-ip level
break;
}
//PT(("psyc:checksum result %O with packet contents %O\n", ctrust, checkpack))
// a quick solution.. maybe we should do SASL instead, or maybe
// we should let the other side specify its =_trust whenever the
// packet is auth'd by a checkpack
if (mc == "_request_circuit_trust") {
// oops.. major error in logic here.. i should be sending a challenge first!!
if (ctrust > 6) {
trustworthy = ctrust;
croak("_echo_circuit_trust",
"You have gained my trust.");
return 1;
}
croak("_failure_invalid_circuit_trust",
"Sorry, could not verify your authorization.");
return 1;
}
}
if (ctrust) cvars["_INTERNAL_trust"] = ctrust;
#else
// good placement here?
if (mc == "_request_circuit_trust") {
croak("_failure_invalid_circuit_trust",
"Sorry, could not verify your authorization.");
return 1;
}
#endif
#endif // PIKE
#if 1 // def DONT_MERGE
// das klappert irgendwie nicht als pvar TODO
cvars["_INTERNAL_origin"] = ME;
// wieso nicht? warum nicht lieber den bug suchen statt
// hier was rumzuhacken? vermutlich kann man es nicht
// oben zuweisen, muss also in ein create()
#endif
// this may only be set by user:msg()
m_delete(cvars, "_nick_verbatim");
// psyced never acts as multiplexing client proxy (as yet)
// so we have no reason to accept this from elsewhere
// and risk to forward it somewhere (we could aswell rename
// into _INTERNAL_target_forward to ensure it not causing
// an outgoing security problem and still be available)
m_delete(cvars, "_target_forward");
#ifdef PSYC_TCP
# ifdef REPATCHING
next_input_to(#'parse);
# endif
# if 0 //ndef HOST_CHECKS
unless (netloc) {
// lets see if the ip number has a name
if (query_ip_name() != query_ip_number()) {
netloc = "psyc://"+query_ip_name();
// peerport has either positive or negative value
if (peerport) netloc += ":"+peerport;
D2(D(netloc+ " resolved name registered\n");)
register_target( netloc );
}
}
# endif
#endif
// backward? compatibility to jaPSYC library
// if (abbrev("_diminish", mc)) {
// diminish(mc[9..], buffer);
// return restart();
// }
if (t = cvars["_context"]) {
// detect when _context contains a local object
// which of course should never happen according
// to routing rules. should we check for psyc_object()?
// no, we should check the validity of the hostnames
// sent to us, then it couldn't possibly be a local object.
if (objectp(t)) {
// and no, it's not in pvars
PP(("psyc:parse got object!? %O.", t));
context = t;
// lowercase policy
} else context = cvars["_context"] = lower_case(t);
}
if (t = cvars["_source"]) {
// "Link to [_source] established.. uses this
// and place/slave too (although improperly)
//m_delete(cvars, "_source");
// if a context is talking, we recognize local sources
// this may not be up to date with new routing syntax :(
unless (context && (source = psyc_object(t))) {
#if 0 // currently we don't use any relative object specs
i = strstr(t, "//");
if (i < 0) { // a relative object spec! experimental!!
// lets guess the remote UNL
source = "psyc://"+peeraddr;
// we allow with or without leading slash
if (t[0] != '/') source += "/";
// we used to care about a trailing slash, why?
//if (t[strlen(t)-1] != '/') t += "/";
// maybe we should strip trailing slashes instead
source += t;
} else if (i == 0) { // a shortened object spec
source = "psyc:"+t;
} else source = t;
#endif
// lowercase policy
source = lower_case(t);
}
#if 0
} else {
source = "psyc://"+peeraddr+"/";
# ifdef REPATCHING
patches[source] = source; // dont parse this
# endif
#endif
}
// checking _source_XXX etc should be generically done using a
// flag in the routing vars share thing..
// also, can these safely move into one of the ifs above? TODO
if (t = cvars["_source_relay"]) {
// recognize local psyc object
if (t = psyc_object(t)) {
P2(("local _source_relay %O for %O from %O, cont %O\n",
t, mc, source, context))
cvars["_source_relay"] = t;
} else if (stringp(t)) {
// otherwise: lowercase policy
cvars["_source_relay"] = lower_case(t);
}
}
if (t = cvars["_source_identification"]) {
// recognize local psyc object
if (t = psyc_object(t)) {
P2(("local _source_identification %O for %O from %O, cont %O\n",
t, mc, source, context))
cvars["_source_identification"] = t;
} else if (stringp(t)) {
// otherwise: lowercase policy
cvars["_source_identification"] = lower_case(t);
}
}
P3(("parse1: source is %O, context is %O\n", source, context))
// other side doesn't *have* to provide a source..
if (trustworthy > 6 && (t2 = context || source))
// according to http://about.psyc.eu/Routing we should
// not receive a message with both context and source in
// a long time, so the order of OR is irrelevant here.
register_target(t2);
// cache the physical source string so we don't need to
// parse it later
#ifdef REPATCHING // {{{
else {
// virtual hosting support müsste evtl hierhin.. TODO
if (context) {
// now it becomes the job of the person.c to figure
// out if the context is legitimate and trustworthy
// psyc1.1 type multicasting not supported here
context = cvars["_context"] = repatch(context);
} else if (source) {
D1( if (objectp(source)) D("something went wrong.\n"); )
cvars["_source_verbatim"] = source;
source = repatch(source);
}
}
P3(("parse2: source is %O, context is %O\n", source, context))
#endif // REPATCHING }}}
if (t = cvars["_target"]) {
// this isn't used anywhere, apparently
// but the recipient may care to know how he got called
//m_delete(cvars, "_target");
o = psyc_object(t); // find the object matching the _target
if (!o && strlen(t)) {
mixed u = parse_uniform(t, 1);
// be tolerant, allow for scheme-independent notation
// //unless (u[UScheme] && u[UScheme] == "psyc") {
// unless (u[UScheme] && strlen(u[UScheme])) {
// croak("_failure_unsupported_target_relative",
// "No support for relative targets yet.");
// QUIT
// }
unless (u[UHost] && strlen(u[UHost])) {
croak("_failure_unsupported_notation_location",
"No support for fancy UNLs yet.");
QUIT
}
#ifdef NONIX
if (strlen(u[UResource]) > 1)
cvars["_INTERNAL_nick_target"] = u[UResource][1..];
#endif
P3(("DEBUG: is_localhost is %O for %O of %O\n",
is_localhost(u[UHost]), u[UHost], u))
unless (is_localhost(u[UHost])) {
if (trustworthy > 7) {
P1(("RELAYING permitted for %O to %O (%O)\n",
source, t, ME))
unless (cvars["_source_relay"])
cvars["_source_relay"] = source;
source = cvars["_source"] || SERVER_UNIFORM;
// relay the message!
// this is used by procmail for example, whenever
// it needs to send to an xmpp: recipient.
sendmsg(t, mc, buffer==""? 0: buffer, cvars, source);
return restart();
}
P1(("RELAYING denied from %O to %O (%O)\n",
source, t, ME))
#if 0
monitor_report("_warning_unsupported_relay",
S("%O is trying to find %O here. Relaying denied.\n", ME, t));
croak("_failure_unsupported_relay",
//"Well done mate, you crashed me.");
"Relaying denied: [_host] is not a hostname of ours.",
([ "_host": u[UHost] ]));
// TODO: we quit here to not do the same hash-lookup
// in rootmsg again.
// (didn't get it? nevermind.. it's just el's sick humor)
QUIT
#endif
}
// .. yes.. add is_localhost check here, but without callback
// as we need a whitelist of allowed localhosts anyway..
// or would you like it if somebody added a CNAME stinkpfau.go
// to your server? no. are you going to blacklist him? no
// just like a webserver you will act depending on your configured
// hostnames, even if we let the end user configure it by specifying
// the hostname he likes his identities to be shown as...
// .. /set hostname psyc.duftepfau.go ... or was it /set id ?
//
// implement proxy service? it already exists for trustworthy senders
// do any checking for gateway schemes? TODO
if (u) o = find_psyc_object(u);
}
if (o) {
#ifdef PSYC_TCP
P2(("TCP[%s] => %O: %s\n", peeraddr, o || t, mc))
#else
PT(("UDP[%s] => %O: %s\n", peeraddr, o || t, mc));
#endif
P4(("%O's %s(%O) to %O\n", source, mc, buffer, o))
#ifdef TAGGING
// should we no longer use the monster sprintf to keep our callbacks, then
// the callbacks should in many cases ensure they got the reply from a
// legitimate sender.. or we'll have a most interesting mitm-zone here..
// then again, in some other cases, that is exactly what tags are for!
// FIXME: there is code for executing callbacks in
// entity.c - why is this here?
if (cvars["_tag_reply"] &&
objectp(o) && o->execute_callback(cvars["_tag_reply"],
({ source, mc, buffer == "" ? 0 : buffer, cvars }))) {
// NOOP
} else
#endif
if (objectp(o))
o -> msg(source, mc,
buffer == "" ? 0 : buffer, cvars, 0, t);
else
PP(("%O's %s(%O) to %O\n", source, mc, buffer, o));
#if 0
} else if (stringp(o) && strlen(o)) {
// should croak using sendmsg, huh?
croak("_error_unknown_target",
"No such object: [_target_given]",
([ "_target_given": t ]) );
// how does one refer to the incoming _target
// variable? _given doesnt look good to me.
// new: see _error_illegal_uniform below
#endif
#ifdef REPATCHING
return restart();
#else
return 1;
#endif
} else {
if (source) sendmsg(source, "_error_illegal_uniform",
"[_uniform] is not a legal address here.",
([ "_uniform": cvars["_target"],
"_tag_reply": cvars["_tag"] ]) );
else croak("_error_illegal_uniform",
"[_uniform] is not a legal address here.",
([ "_uniform": cvars["_target"],
"_tag_reply": cvars["_tag"] ]) );
}
}
if (context) {
#ifndef __PIKE__ // TPD
o = find_context(context);
unless(o) {
// no one is interested / online
// is this an abuse or a feature?
// fippo: feature, context may be 0
// if no one who is normally listening is online
return restart();
}
if (objectp(o))
o -> castmsg(source, mc,
buffer == "" ? 0 : buffer, cvars);
// this effectively ignores any _targets that may have
// been submitted with the message.. do we like that? TODO
#endif
return restart();
}
// else if (t) return _error_rejected_relay_outgoing TODO
t2 = SERVER_UNIFORM;
// this part is new and maybe can be optimized.. TODO
// the main optimization would be to check for is_localhost
// with host part of target
if (t == t2 || (char_from_end(t2, 1) == '/' && t+"/" == t2)) {
// i know that this check sucks... but as we dont normalize
// uniforms...
// it would be much better to check if only host is set
// and then use is_localhost
sendmsg("/", mc, buffer == "" ? 0 : buffer, cvars, source);
return restart();
}
rootMsg(source, mc, buffer == "" ? 0 : buffer, cvars, t);
#ifdef REPATCHING
restart();
#endif
}
return 1;
}
#ifdef PSYC_TCP
vamixed startParse(string a) {
if (a == ".") { // old S_GLYPH_PACKET_DELIMITER
restart();
if (isServer()) greet();
}
# if defined(SPYC_PATH) && defined(USE_SPYC)
else if (a == "|") { // new S_GLYPH_PACKET_DELIMITER
object o = clone_object(SPYC_PATH "server");
unless (o && exec(o, ME) && o->logon(0)) {
croak("_failure_object_creation_server",
"Could not instantiate PSYC parser. Weird.");
QUIT
}
// if (isServer()) o->greet();
o->feed("|\n");
return 1;
}
# endif
# ifdef SWITCH2PSYC
// minor bug: we send the . right after <switched/> without a
// newline as xmpp doesn't require that and psyc doesn't want
// it. still ldmud puts the . in the wrong buffer, so let's
// just be tolerant and accept an empty newline for starting
// under this circumstance. however we should fix this one day.
else if (a == "") restart();
// how can we avoid the buffer from being re-input to the
// exec'd object? this however circumvents it
// in fact it seems to be a bug in older ldmud versions
// newer versions instead have the bug of providing an empty buffer
else if (abbrev("<?xml", a)) {
PT(("SWITCH2PSYC: re-encountered old buffer. ignoring: %O\n", a))
next_input_to(#'startParse);
return 1;
}
# endif
# ifdef MESSAGE_SEND_PROTOCOL
// only activate this if you use the PSYC port of lords
// minimal compliant MSP implementation ;)
// would love to code a bit more of it, but it is completely pointless
else if (a[0] == 'B' || a[0] == 'A') {
write("-MSP is of historic and cultural value, but please upgrade to PSYC.\n");
QUIT
}
#endif
else {
PT(("PSYC startParse got %O from %O\n", a, query_ip_number()))
croak("_error_syntax_initialization",
"The protocol begins with a dot on a line by itself.");
// experiencing a loop here, because some implementations
// try immediate reconnect. idea: in most places where we
// QUIT we should put the tcp link on hold instead, and
// close it only several seconds later. TODO
QUIT
}
pvars["_INTERNAL_source"] = "psyc://"+peeraddr+"/";
next_input_to(#'parse);
return 1;
}
#endif
string qOrigin() { return origin_unl; }
void sOrigin(string origin) { unless (origin_unl) origin_unl = origin; }
#endif /* !FORK */