mirror of
https://github.com/ChronosX88/psyced.git
synced 2024-11-12 21:41:04 +00:00
494 lines
14 KiB
C
494 lines
14 KiB
C
// vim:foldmethod=marker:syntax=lpc:noexpandtab
|
|
// $Id: circuit.c,v 1.33 2008/03/29 16:00:02 fippo Exp $
|
|
|
|
#include "psyc.h"
|
|
#include <net.h>
|
|
#include <url.h>
|
|
#include <tls.h>
|
|
#include <text.h>
|
|
|
|
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);
|
|
}
|