// 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 #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 ? // 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. // 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); #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"] || query_server_unl(); // 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 = query_server_unl(); // 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(); } # ifdef SPYC_PATH 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 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("