// vim:foldmethod=marker:syntax=lpc:noexpandtab // $Id: circuit.c,v 1.33 2008/03/29 16:00:02 fippo Exp $ #include "psyc.h" #include #include #include #include inherit NET_PATH "trust"; inherit NET_PATH "spyc/parse"; virtual inherit NET_PATH "output"; volatile string peerhost; volatile string peeraddr; volatile string peerip; volatile int peerport; volatile string netloc; #ifndef NEW_RENDER # define NEW_RENDER #endif #include "edit.i" // this is completely anti-psyc. it should take mcs as arguments // and look up the actual message from textdb.. FIXME #define CIRCUITERROR(reason) { debug_message("PSYC CIRCUIT ERROR: " reason); \ croak("_error_circuit", "circuit error: " \ reason); \ return 0; \ } mapping instate; mapping outstate; mapping legal_senders; volatile int flags = 0; void circuit_msg(string mc, mapping vars, string data); // prototype varargs int msg(string source, string mc, string data, mapping vars, int showingLog, mixed target); // prototype protected void quit(); // prototype void runQ(); int isServer() { return 0; } void feed(string data) { input_to(#'feed, INPUT_IGNORE_BANG); ::feed(data); } // yes, this is a funny implementation of croak // it does not use msg(). Yes, that is intended varargs mixed croak(string mc, string data, vamapping vars, vamixed source) { binary_message(sprintf("\n%s\n%s\n|\n", mc, data)); remove_interactive(ME); destruct(ME); return 0; } // gets called during socket logon int logon(int success) { sAuthHosts(([ ])); // reset authhosts legal_senders = ([ ]); instate = ([ "_INTERNAL_origin" : ME]); outstate = ([ ]); #ifdef __TLS__ mixed cert; if (tls_available() && tls_query_connection_state(ME) == 1 && mappingp(cert = tls_certificate(ME, 0))) { mixed m, t; if (cert[0] != 0) { // log error 17 + cert here // and goodbye. P0(("%O encountered a cert verify error %O in %O\n", ME, cert[0], cert)) remove_interactive(ME); return 0; } if (m = cert["2.5.29.17:dNSName"]) { // FIXME: this does not yet handle wildcard DNS names P1(("%O believing dNSName %O\n", ME, m)) // probably also: register_target? // but be careful never to register_target wildcards if (stringp(m)) sAuthenticated(NAMEPREP(m)); else foreach(t : m) sAuthenticated(NAMEPREP(t)); } //#ifdef _flag_allow_certificate_name_common // to be switched this year #ifndef _flag_disallow_certificate_name_common // assume that CN is a host // as this is an assumption only, we may NEVER register_target it // note: CN is deprecated for good reasons. else if (t = cert["2.5.4.3"]) { P1(("%O believing CN %O\n", ME, t)) sAuthenticated(NAMEPREP(t)); } #endif if (m = tls_query_connection_info(ME)) { P2(("%O is using the %O cipher.\n", ME, m[TLS_CIPHER])) // shouldn't our negotiation have ensured we have PFS? if (stringp(t = m[TLS_CIPHER]) &&! abbrev("DHE", t)) { // croak("_warning_circuit_encryption_cipher", // "Your cipher choice does not provide forward secrecy."); monitor_report( "_warning_circuit_encryption_cipher_details", object_name(ME) +" · using "+ t +" cipher"); //debug_message(sprintf( // "TLS connection info for %O is %O\n", ME, m)); //QUIT // are we ready for *this* !??? } } } #endif peerip = query_ip_number(ME) || "127.0.0.1"; enable_binary(ME); input_to(#'feed, INPUT_IGNORE_BANG); call_out(#'quit, 90); flags = TCP_PENDING_TIMEOUT; parser_init(); // FIXME unless(isServer()) { emit("|\n"); // initial greeting msg(0, "_request_features", 0); } return 1; } int disconnected(string remaining) { // i love to copy+paste source codes! thx for NOT sharing.. grrr #if DEBUG > 0 if (remaining && (!stringp(remaining) || strlen(remaining))) PP(("%O ignoring remaining data from socket: %O\n", ME, remaining)); #endif // wow.. a sincerely expected disconnect! if (flags & TCP_PENDING_DISCONNECT) return 1; monitor_report("_failure_network_circuit_disconnect", object_name(ME) +" · lost PSYC circuit"); return 0; // unexpected } // respond to the first empty packet first_response() { emit("|\n"); } // processes routing header variable assignments // basic version does no state mapping process_header(mixed varops) { mapping vars = ([ ]); foreach(mixed vop : varops) { string vname = vop[0]; switch(vop[1]) { case C_GLYPH_MODIFIER_ASSIGN: instate[vname] = vop[2]; // fall thru case C_GLYPH_MODIFIER_SET: vars[vname] = vop[2]; break; case C_GLYPH_MODIFIER_AUGMENT: case C_GLYPH_MODIFIER_DIMINISH: case C_GLYPH_MODIFIER_QUERY: CIRCUITERROR("header modifier with glyph other than ':', this is not implemented") break; default: CIRCUITERROR("header modifier with unknown glyph") break; } // FIXME: not every legal varname is a mmp varname // look at shared_memory("routing") if (!legal_keyword(vname) || abbrev("_INTERNAL", vname)) { CIRCUITERROR("illegal varname in header") } } return vars; } // handling of packets received void done(mixed header_vars, mixed varops, mixed method, mixed body) { string vname; mixed vop; // value operation mapping vars; string t; // check that method is a valid keyword if (method && !legal_keyword(method)) { CIRCUITERROR("non legal method"); } // copy() + occasional double modifier ops should be more // efficient than merge at every packet --lynX // no one cares about "efficiency" here. please proof your // bold statements with benchmarks anyway vars = header_vars + instate; // FIXME: this can happen earlier, e.g. in parse.c after // process_header // check _source/_context // this check can be skipped if _source and _context are empty if ((t = vars["_context"] || vars["_source"])) { array(mixed) u; unless (u = parse_uniform(t)) { CIRCUITERROR("logical source is not an uniform\n") } unless (qAuthenticated(NAMEPREP(u[UHost]))) { CIRCUITERROR("non-authenticated host\n") } } // check that _target is hosted by us // this check can be skipped if _target is not set if ((t = vars["_target"])) { array(mixed) u; unless (u = parse_uniform(t)) { CIRCUITERROR("target is not an uniform\n") } // FIXME relaying support here? if (!(is_localhost(u[UHost])) || u[UHost] == "localhost") { CIRCUITERROR("target is not configured on this server\n") } } // FIXME: i dont like this block... maybe we decode each variable // when setting it? // that would also fit with 0 as varname deletion // below foreach(vop : varops) { vname = vop[0]; // TODO unpack _amount // TODO unpack _time if (abbrev("_list", vname)) { mixed plist = list_parse(vop[2]); if (plist == -1) { CIRCUITERROR("could not parse list"); } vop[2] = plist; } } // FIXME deliver packet // this should be a separate function PT(("vars is %O\n", vars)) PT(("method %O\nbody %O\n", method, body)) PT(("packet done\n")) // delivery rules as usual, but if (vars["_context"]) { mixed context; mixed context_state; mixed source, target; if (vars["_source"]) { P0(("invalid _context %O with _source %O\n", context, vars["_source"])) CIRCUITERROR("invalid usage of context with _source"); } context = find_context(vars["_context"]); if (!objectp(context)) { P0(("context %O not found?!\n", vars["_context"])) return; } context_state = context->get_state(); // apply varops to context state foreach(vop : varops) { vname = vop[0]; if (!legal_keyword(vname) || abbrev("_INTERNAL", vname)) { CIRCUITERROR("illegal varname in psyc") } switch(vop[1]) { // the glyph case C_GLYPH_MODIFIER_SET: vars[vname] = vop[2]; break; case C_GLYPH_MODIFIER_ASSIGN: vars[vname] = context_state[vname] = vop[2]; break; case C_GLYPH_MODIFIER_AUGMENT: if (!abbrev("_list", vname)) { CIRCUITERROR("psyc modifier + with non-list arg") } // FIXME: duplicates? context_state[vname] += vop[2]; PT(("current state is %O, augment %O\n", context_state[vname], vop[2])) break; case C_GLYPH_MODIFIER_DIMINISH: if (!abbrev("_list", vname)) { CIRCUITERROR("psyc modifier + with non-list arg") } PT(("current state is %O, diminish %O\n", context_state[vname], vop[2])) foreach(mixed item : vop[2]) context_state[vname] -= ({ item }); PT(("after dim: %O\n", context_state[vname])) break; case C_GLYPH_MODIFIER_QUERY: CIRCUITERROR("psyc modifier ? not implemented") break; } } vars = vars + context_state; // FIXME: is it legal to do this if this has _target? // there should be no mods then anyway context->commit_state(context_state); if (vars["_target"]) { // FIXME: delivery copycat from below // beware: source is set to 0 here as it may not be present target = find_psyc_object(parse_uniform(vars["_target"])); PT(("target is %O\n", target)) // FIXME: net/entity can not yet deal with 0 method // if (objectp(context)) { context->msg(0, method || "", body, vars, 0, target); } else { // FIXME: proper croak back to sender here P0(("context %O for unicast to %O not found???\n", target)) } } else { if (vars["_source_relay"]) { mixed localrelay; if ((localrelay = psyc_object(vars["_source_relay"]))) { P0(("local relay %O\n", localrelay)) vars["_source_relay"] = localrelay; } else { // NORMALIZE UNIFORM vars["_source_relay"] = lower_case(vars["_source_relay"]); } } if (objectp(context)) { // do we need more local object detection here? context -> castmsg(source, method || "", body, vars); } else { // empty contexts are not bad currently // in the current implementation it only means that no one // interested in that context is online right now // FIXME: lines above are about the old stuff where we did // not have context state } } } else { if (!vars["_target"] && !vars["_source"]) { circuit_msg(method, vars, body); } else { string source; mixed target; if (!vars["_source"]) { // FIXME: where to set netloc in active if (!netloc) { // set in sender after _request_features // FIXME: this is wrong CIRCUITERROR("Did you forget to request circuit features?"); } source = netloc; } else { // FIXME: a macro NORMALIZE_UNIFORM that may do lower_case please // not a simple lower_case source = lower_case(vars["_source"]); } // source was checked either via x509 or dns before // so it is 'safe' to do this register_target(source); // deliver FIXME same code above if (!vars["_target"]) { target = find_object(NET_PATH "root"); } else { target = find_psyc_object(parse_uniform(vars["_target"])); } PT(("target is %O\n", target)) // FIXME: net/entity can not yet deal with 0 method if (objectp(target)) target->msg(source, method || "", body, vars); else { // FIXME: proper croak back to sender here P0(("target %O not found???\n", target)) } } } ::done(header_vars, varops, method, body); } // request sender authentication and/or target acknowledgement // from the remote side void sender_verification(array(string) sourcehosts, array(string) targethosts) { // FIXME: wrong variables here mapping vars = ([ "_list_sources_hosts" : sourcehosts, "_list_targets_hosts" : targethosts, "_tag" : RANDHEXSTRING ]); // assumption: we have already resolved all targethosts and // they point to the remote ip foreach(string ho : targethosts) { sAuthenticated(ho); } msg(0, "_request_verification", 0, vars); } // receives a msg from the remote side // note: this is circuit-messaging void circuit_msg(string mc, mapping vars, string data) { switch(mc) { case "_request_verification": if (tls_query_connection_state(ME) == 0) { array(string) targethosts = ({ }); foreach(string ho : vars["_list_targets_hosts"]) { if (is_localhost(ho)) { targethosts += ({ ho }); } } if (sizeof(vars["_list_sources_hosts"]) == 1) { // doing multiple resolutions in parallel is more complicated string ho = vars["_list_sources_hosts"][0]; if (qAuthenticated(ho)) { P0(("warning: trying to reverify authenticated host %O",ho)) } else { dns_resolve(ho, (: // FIXME: psyc/parse::deliver is much better here mixed rv = (["_list_targets_accepted_hosts":targethosts]); if (vars["_tag"]) rv["_tag_reply"] = vars["_tag"]; if ($1 == peerip) { sAuthenticated(NAMEPREP(ho)); rv["_list_sources_verified_hosts"] = ({ ho }); } else { rv["_list_sources_rejected_hosts"] = ({ ho }); } msg(0, "_notice_verification", 0, rv); return; :)); } } else { // FIXME!!!! CIRCUITERROR("sorry, no more than one element in _list_sources_hosts currently"); } // keep tag if present!!! // resolve all of _list_sources_hosts // look at _list_targets_hosts and determine localhostiness } else { CIRCUITERROR("_request_verification is not allowed on TLS circuits."); // _request_verification is not allowed on tls circuits } break; case "_notice_features": // FIXME: watch for _list_using_modules if (flags & TCP_PENDING_TIMEOUT) { P0(("removing call out\n")) remove_call_out(#'quit); flags -= TCP_PENDING_TIMEOUT; } sTextPath(); if (tls_query_connection_state(ME) == 0) { // start hostname verification // rather: look at Q and look for the hostnames we need sender_verification(({ SERVER_HOST }), ({ peerhost })); } else { if (function_exists("runQ")) { runQ(); } } break; case "_notice_verification": P0(("_notice verification with %O\n", vars)) if (function_exists("runQ")) { runQ(); } break; default: P0(("%O got circuit_msg %O, not implemented\n", ME, mc)) break; } } // delivers a message to the remote side varargs int msg(string source, string mc, string data, mapping vars, int showingLog, mixed target) { string buf = ""; unless(vars) vars = ([ ]); buf = psyc_render(source, mc, data, vars, showingLog, target); #ifdef _flag_log_sockets_PSYC log_file("RAW_PSYC", "» %O\n%s\n", ME, buf); #endif return emit(buf); }