1
0
Fork 0
mirror of git://git.psyced.org/git/psyced synced 2024-08-15 03:25:10 +00:00

let the past begone in cvs land. welcome to igit igit!

This commit is contained in:
PSYC 2009-01-26 20:21:29 +01:00
commit 4e601cf1c7
509 changed files with 77963 additions and 0 deletions

4
world/net/spyc/active.c Normal file
View file

@ -0,0 +1,4 @@
// $Id: active.c,v 1.1 2008/01/30 17:06:48 lynx Exp $ // vim:syntax=lpc
#include "psyc.h"
#include "../psyc/active.c"

493
world/net/spyc/circuit.c Normal file
View file

@ -0,0 +1,493 @@
// 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);
}

4
world/net/spyc/common.c Normal file
View file

@ -0,0 +1,4 @@
// $Id: common.c,v 1.1 2008/01/30 17:06:48 lynx Exp $ // vim:syntax=lpc
#include "psyc.h"
#include "../psyc/common.c"

4
world/net/spyc/edit.i Normal file
View file

@ -0,0 +1,4 @@
// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: edit.i,v 1.8 2008/02/19 16:28:41 lynx Exp $
#include "../psyc/edit.i"

4
world/net/spyc/library.i Normal file
View file

@ -0,0 +1,4 @@
// $Id: library.i,v 1.1 2008/02/07 09:33:46 fippo Exp $ // vim:syntax=lpc
#include "psyc.h"
#include "../psyc/library.i"

398
world/net/spyc/parse.c Normal file
View file

@ -0,0 +1,398 @@
// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: parse.c,v 1.24 2008/03/29 16:00:02 fippo Exp $
//
#include "psyc.h"
#include <net.h>
#include <input_to.h>
private string buffer;
private string body_buffer;
int state;
int body_len;
int may_parse_more;
// tempoary used to hold assigment lists vname -> ({ glyph, state, vvalue })
array(mixed) tvars;
mapping hvars;
// this is completely anti-psyc. it should take mcs as arguments
// and look up the actual message from textdb.. FIXME
#define PARSEERROR(reason) { debug_message("PSYC PARSE ERROR: " reason "\n"); \
croak("_error_syntax_broken", "Failed parsing: " \
reason); \
return 0; \
}
#define DELIM S_GLYPH_PACKET_DELIMITER "\n"
#define C_LINEFEED '\n'
#define MVAR_GLYPH 0
#define MVAR_STATE 1
#define MVAR_VALUE 2
step(); // prototype
// reset parser state
void parser_reset() {
if (state != PSYCPARSE_STATE_BLOCKED)
state = PSYCPARSE_STATE_HEADER;
body_len = 0;
body_buffer = 0;
may_parse_more = 1;
tvars = ({ });
hvars = ([ ]);
}
// initialize the parser
void parser_init() {
buffer = "";
parser_reset();
state = PSYCPARSE_STATE_GREET; // AFTER reset
}
// it is sometimes useful to stop parsing
void interrupt_parse() {
state = PSYCPARSE_STATE_BLOCKED;
}
// and resume after some blocking operation is done
void resume_parse() {
state = PSYCPARSE_STATE_HEADER;
}
// input data to the buffer
void feed(string data) {
# ifdef _flag_log_sockets_PSYC
log_file("RAW_PSYC", "» %O\n%s\n", ME, data);
# endif
buffer += data;
do {
may_parse_more = 0;
step();
} while (may_parse_more);
}
// overload this as needed
varargs mixed croak(string mc, string data, vamapping vars, vamixed source) {
return 0;
}
// called when a complete packet has arrived
void done(mixed header_vars, mixed varops, mixed method, mixed body) {
parser_reset();
}
// processes routing header variable assignments
// basic version does no state
mapping process_header(mixed varops) {
mapping vars = ([ ]);
// apply mmp state
foreach(mixed vop : varops) {
string vname = vop[0];
switch(vop[1]) {
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:
case C_GLYPH_MODIFIER_ASSIGN:
PARSEERROR("header modifier with glyph other than ':', this is not implemented")
break;
default:
PARSEERROR("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)) {
PARSEERROR("illegal varname in header")
}
}
return vars;
}
// parse the header part of the packet
// i.e. that is all mmp modifiers
// switch to content buffering mode afterwards
void parse_header() {
if (!strlen(buffer)) return;
if (buffer[0] == C_GLYPH_PACKET_DELIMITER) {
if (strlen(buffer) < 2)
return;
if (buffer[1] == C_LINEFEED) {
buffer = buffer[2..];
hvars = process_header(tvars);
tvars = ({ });
done(hvars, tvars, 0, 0);
} else {
// this one is sth like |whatever
// actually this should be noglyph i think
PARSEERROR("strange thing")
}
} else if (buffer[0] == C_LINEFEED) {
// state transition from parsing header to buffering body
buffer = buffer[1..];
hvars = process_header(tvars);
tvars = ({ });
// FIXME: validate source/context here
state = PSYCPARSE_STATE_CONTENT;
if (hvars["_length"])
body_len = to_int(hvars["_length"][MVAR_VALUE]);
step();
} else { // parse mmp-header
int fit;
int glyph;
string vname, vvalue;
switch(buffer[0]) {
case C_GLYPH_MODIFIER_SET:
case C_GLYPH_MODIFIER_ASSIGN:
case C_GLYPH_MODIFIER_AUGMENT:
case C_GLYPH_MODIFIER_DIMINISH:
case C_GLYPH_MODIFIER_QUERY:
glyph = buffer[0];
buffer = buffer[1..];
break;
default:
PARSEERROR("noglyph")
}
fit = sscanf(buffer, "%.1s%t", vname);
if (fit != 1) {
PARSEERROR("vname")
}
buffer = buffer[strlen(vname)..];
switch(buffer[0]) {
case '\t':
fit = sscanf(buffer, "\t%s\n%.0s", vvalue, buffer);
if (fit != 2) {
PARSEERROR("simple-arg")
}
break;
case '\n': // deletion
// this is currently implemented as "vvalue is 0" internally
// and must handled in done() when merging
buffer = buffer[1..];
break;
default:
PARSEERROR("arg")
}
tvars += ({ ({ vname, glyph, vvalue }) });
step();
}
}
// parse all psyc-modifiers
// this differes from the mmp modifiers in the sense that
// a) packet is known to be complete here
// b) psyc modifiers may have binary args
void parse_psyc() {
while(1) { // slurp in all psyc-modifiers
int fit, len;
int glyph;
string vname, vvalue;
switch(body_buffer[0]) {
case C_GLYPH_MODIFIER_SET:
case C_GLYPH_MODIFIER_ASSIGN:
case C_GLYPH_MODIFIER_AUGMENT:
case C_GLYPH_MODIFIER_DIMINISH:
case C_GLYPH_MODIFIER_QUERY:
glyph = body_buffer[0];
body_buffer = body_buffer[1..];
break;
default:
// this is the method
return;
}
fit = sscanf(body_buffer, "%.1s%t", vname);
if (fit != 1) {
PARSEERROR("vname")
}
body_buffer = body_buffer[strlen(vname)..];
switch(body_buffer[0]) {
case ' ':
fit = sscanf(body_buffer, " %d\t%.0s", len, body_buffer);
if (fit != 2) {
PARSEERROR("binary-arg length")
}
if (len < 0) {
PARSEERROR("negative binary length")
}
if (strlen(body_buffer) < len) {
PARSEERROR("not enough to read binary arg, may not happen")
}
vvalue = body_buffer[..len-1];
body_buffer = body_buffer[len..];
if (body_buffer[0] != C_LINEFEED) {
PARSEERROR("binary terminal")
}
body_buffer = body_buffer[1..];
break;
case '\t':
fit = sscanf(body_buffer, "\t%s\n%.0s", vvalue, body_buffer);
if (fit != 2) {
PARSEERROR("simple-arg")
}
break;
case '\n':
switch(glyph) {
case C_GLYPH_MODIFIER_ASSIGN:
// delete this context's state
PARSEERROR("tbd")
// unfortunately the routing hasn't been processed at
// this moment yet, so we don't know whose context this
// is and if it is legitimate. we first have to fix the
// processing of the routing layer before we can implement
// anything of this
continue;
case C_GLYPH_MODIFIER_SET:
// remember to temporarily ignore state
PARSEERROR("tbd")
continue;
case C_GLYPH_MODIFIER_QUERY:
// mark that the next reply packet should
// contain a state sync
PARSEERROR("tbd")
continue;
default:
PARSEERROR("undefined operation")
}
return;
default:
PARSEERROR("arg")
}
tvars += ({ ({ vname, glyph, vvalue }) });
}
}
// parse completed content
void parse_content() {
int fit;
string method, body;
parse_psyc();
// ASSERT strlen(buffer)
if (body_buffer[0] == C_LINEFEED) {
PARSEERROR("empty method")
}
// FIXME: i am not sure if this is correct...
if (body_buffer == S_GLYPH_PACKET_DELIMITER) {
P0(("encountered packet delimiter in packet body? how's that?\n"))
done(hvars, tvars, 0, 0);
return;
}
fit = sscanf(body_buffer, "%.1s\n%.0s", method, body_buffer);
if (fit != 2 || !legal_keyword(method)) {
croak("_error_illegal_method",
"That's not a valid method name.");
return; // NOTREACHED
}
// mhmm... why does body_buffer still contain the newline?
// because the newline is by definition not part of the body!
if (strlen(body_buffer)) body = body_buffer[..<2];
done(hvars, tvars, method, body);
}
// buffer content until complete
// then parse it
// note: you could overload this, if you just want to route
// the packet
void buffer_content() {
int t;
if (body_len && strlen(buffer) >= body_len + 3) {
// make sure that the packet is properly terminated
if (buffer[body_len..body_len+2] != "\n" DELIM) {
PARSEERROR("binary packet delimiter")
}
body_buffer = buffer[..body_len];
buffer = buffer[body_len+3..];
parse_content();
} else if ((t = strstr(buffer, "\n" DELIM)) != -1) {
body_buffer = buffer[..t];
buffer = buffer[t+3..];
parse_content();
} else {
}
}
// respond to the first empty packet
void first_response() {
P0(("parser::first_response called. overload this!"))
}
// parser stepping function
void step() {
if (!strlen(buffer))
return;
switch(state) {
case PSYCPARSE_STATE_HEADER:
parse_header();
break;
case PSYCPARSE_STATE_CONTENT:
buffer_content();
break;
case PSYCPARSE_STATE_BLOCKED:
// someone requested to stop parsing - e.g. _request_features circuit
// message
break;
case PSYCPARSE_STATE_GREET: // wait for greeting
if (strlen(buffer) < 2)
return;
if (buffer[0..1] == DELIM) {
state = PSYCPARSE_STATE_HEADER;
buffer = buffer[2..];
first_response();
step();
} else {
croak("_error_syntax_initialization",
"The protocol begins with a pipe and a line feed.");
}
break;
default: // uhm... if we ever get here this is the programmers fault
break;
}
}
// FIXME should be in a standalone module
//#define PARSEERROR(args) debug_message(sprintf("LIST PARSE ERROR: " args));
#define LISTSEP '|'
#define LISTPARSE_FAIL -1
#define LISTMODE_PLAIN 0
#define LISTMODE_BINARY 1
// list parsing function - val is assumed to be stripped of the final LF
mixed list_parse(string val) {
mixed *lv = ({ });
if (val[0] == LISTSEP)
return explode(val[1..], "|");
while(strlen(val)) {
int fit, len;
fit = sscanf(val, "%d %s", len, val);
if (fit != 2) {
// invalid binary fit
return LISTPARSE_FAIL;
}
if (len < 0 || len > strlen(val)) {
// invalid binary length
return LISTPARSE_FAIL;
}
if (len != strlen(val) && val[len] != LISTSEP) {
// listsep not found after binary
return LISTPARSE_FAIL;
}
lv += ({ val[..len-1] });
val = val[len+1..];
}
return lv;
}
#ifdef SELFTESTS
test() {
list_parse("|psyc://example.symlynX.com/~jim|psyc://example.org/~judy");
list_parse("5\tabcde|4\tabcd");
}
#endif

33
world/net/spyc/psyc.h Normal file
View file

@ -0,0 +1,33 @@
/* this is the development directory for
* psyc 1.0.
*
* the entire net/speck directory has to go
* when net/psyc stops being 0.9 and starts
* being 1.0.
*
* please try to make changes by #ifdef SPYC
* where possible, only do a plugin replacement
* of parse.i
*
* why do we need this directory? let me explain:
* if old and new psyc are to co-exist, we need
* differing path names for objects to assign
* to the differing ports. even if we merge the
* parsers and make them coexist by detecting the
* first incoming byte, then we still need a way
* to distinguish outgoing PSYC and SPYC.
* also, merging the two parsers is not likely or
* useful - they are so totally different in
* structure - but we can exec the proper ones
* from psyclpc or internally, after looking at
* the first byte.
*/
#define SPYC
#define PSYCPARSE_STATE_HEADER 0
#define PSYCPARSE_STATE_CONTENT 1
#define PSYCPARSE_STATE_BLOCKED 2
#define PSYCPARSE_STATE_GREET 3
#include <psyc.h>

171
world/net/spyc/server.c Normal file
View file

@ -0,0 +1,171 @@
// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: server.c,v 1.14 2008/03/11 13:42:27 lynx Exp $
//
// the thing that answers on port 4404 of psyced.
#include "psyc.h"
#include <net.h>
#include <services.h>
#define NO_INHERIT
#include <server.h>
// receiving variant
inherit NET_PATH "spyc/circuit";
// keep a list of objects to ->disconnected() when the driver tells us
volatile array(object) disconnect_notifies;
void do_notify_on_disconnect(object user) {
unless(disconnect_notifies)
disconnect_notifies = ({ });
disconnect_notifies += ({ user });
}
// only used by list_sockets()
string qName() {
switch (sizeof(disconnect_notifies)) {
case 0:
return 0;
case 1:
return to_string( disconnect_notifies[0] );
// default:
}
return to_string( sizeof(disconnect_notifies) );
}
//isServer() { return peerport < 0; }
int isServer() { return 1; }
load(ho, po) {
D0 ( if (peerport)
PP(("%O loaded twice for %O and %O\n", ME, peerport, po)); )
peerport = po;
// P3(("loaded server on %O\n", peerport))
return ME;
}
protected quit() { QUIT }
// self-destruct when the TCP link gets lost
disconnected(remaining) {
int rc;
P2(( "%O got disconnected.\n", ME))
// emulate disconnect() for net/psyc/user
if (disconnect_notifies) {
foreach (object t : disconnect_notifies)
if (t) t->disconnected();
}
rc = ::disconnected(remaining);
destruct(ME);
return rc;
}
static void resolved(mixed host, mixed tag) {
PT(("resolved %O to %O\n", peerip, host))
string numericpeeraddr;
mixed uni, psycip;
unless (stringp(host)) {
#if 1 //ndef BETA
if (host == -2) {
monitor_report("_warning_invalid_hostname",
S("%O: %O has an invalid IN PTR", ME,
query_ip_number(ME)));
# ifndef STRICT_DNS_POLICY
// we could instead lower the trust value for this
// host so that it can no longer send messages in
// a federated way, but still link into an identity
// as a client....... TODO.. like this?
if (trustworthy < 6) trustworthy = 1;
host = peerip;
# else
croak("_error_invalid_hostname",
"Your IP address points to a hostname which doesn't point back to the IP.\n"
"You could be trying to spoof you're in somebody else's domain.\n"
"We can't let you do that, sorry.");
return;
# endif
}
else if (host == -1) host = peerip;
else {
P0(("resolved(%O) in %O. but that's impossible.\n",
host, ME))
croak("_failure_invalid_hostname",
"Resolving your IP address triggered an internal error.");
return;
}
#else
// we sent them to beta, so let beta be easy on them
host = peerip;
#endif
}
// maybe dns_rresolve should only return lower_case'd strings
// then we no longer have to do that everywhere else TODO
host = lower_case(host);
#ifdef EXTRA_RRESOLVE
EXTRA_RRESOLVE(host)
#endif
if (trustworthy < 6) {
if (trustworthy = legal_domain(host, peerport, "psyc", 0)) {
if (trustworthy < 3) trustworthy = 0;
}
else {
croak("_error", "Sei nicht so ein Mauerbluemchen");
return;
}
}
// the resolver does not register automatically, so here we go
register_host(peerip, host);
register_host(host);
// register_target( "psyc://"+peeraddr );
numericpeeraddr = peeraddr;
peeraddr = peerhost = host;
// peerport has either positive or negative value
if (peerport && peerport != PSYC_SERVICE) peeraddr += ":"+peerport;
netloc = "psyc://"+peeraddr+"/";
register_target( netloc );
#if 1 // OPTIONAL
// should this server be connected to a psyc client, then the new
// resolved name of the connection may have to be recognized as
// location for the person. finally this code should be unnecessary
// as you should never want to _link a person before seeing the
// _notice_circuit_established. maybe we should even enforce that.
// anyway, here's an attempt to cope with such a situation.. maybe
// it turns out useful someday (lynX after a quick patch by elrid).
//
psycip = "psyc://"+numericpeeraddr+"/";
if (uni = lookup_identification(psycip)) {
register_location(netloc, uni);
// cleanup? are you sure we will never need this again?
register_location(psycip, 0); //cleanup
}
#endif
// PIKE TPD: says psyc://127.0.0.1/ here .. should say
// psyc://localhost:-23232/ instead
P2(("%O resolves as %O (UNI %O)\n", ME, netloc, uni))
if (flags & TCP_PENDING_TIMEOUT) {
P0(("removing call out\n"))
remove_call_out(#'quit);
flags -= TCP_PENDING_TIMEOUT;
}
resume_parse();
sTextPath();
// FIXME: determine response to greeting
// instead of this dummy
msg(0, "_notice_features", 0, tag ? ([ "_tag_reply" : tag ]) : 0);
}
void circuit_msg(string mc, mapping vars, string data) {
switch(mc) {
case "_request_features": // only servers handle _request_features
interrupt_parse();
dns_rresolve(peerip, #'resolved, vars && vars["_tag"]);
break;
default:
return ::circuit_msg(mc, vars, data);
}
}

193
world/net/spyc/udp.c Normal file
View file

@ -0,0 +1,193 @@
// vim:foldmethod=marker:syntax=lpc:noexpandtab
// $Id: udp.c,v 1.3 2008/02/19 16:28:41 lynx Exp $
#include "psyc.h"
#include <net.h>
#include <url.h>
#include <text.h>
inherit NET_PATH "spyc/parse";
#define CIRCUITERROR(reason) { debug_message("PSYC CIRCUIT ERROR: " reason); \
croak("_error_circuit", "circuit error: " \
reason); \
return; \
}
string netloc;
parseUDP2(host, ip, port, msg); // prototype
object load() { return ME; } // avoid a find_object call in obj/master
parseUDP(ip, port, msg) {
dns_rresolve(ip, #'parseUDP2, ip, port, msg);
}
// respond to the first packet - or don't do it
first_response() { }
parseUDP2(host, ip, port, msg) {
// this is an atomic operation. It is never interrupted by another
// call to parseUDP2. Or at least it is not designed to be.
unless(stringp(host))
host = ip; // FIXME: we reject tcp from hosts with invalid pointers
// but not udp???
netloc = "psyc://" + host + ":" + to_string(-port) + "/";
P0(("parseUDP2 from %O == %O port %O\n", host, ip, port))
parser_init();
feed(msg);
}
void done(mixed varops, mixed method, mixed body) {
string vname;
mixed vop; // value operation
mapping vars = ([ ]); // udp is stateless
string t;
// FIXME: ideally, those unpacks would happen after the checks
// that get a packet rejected
// FIXME: this allows binary lists in mmp
foreach(vname, vop : varops) {
// 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;
}
}
// apply mmp state
foreach(vname, vop : varops) {
if (vop[1] == 1) // vname was encountered in psyc part
continue;
switch(vop[0]) {
case C_GLYPH_MODIFIER_SET:
vars[vname] = vop[2];
break;
case C_GLYPH_MODIFIER_ASSIGN:
case C_GLYPH_MODIFIER_AUGMENT:
case C_GLYPH_MODIFIER_DIMINISH:
case C_GLYPH_MODIFIER_QUERY:
CIRCUITERROR("stateful operation in udp mode")
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")
}
}
// 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")
}
}
// check that _target is hosted by us
// this check can be skipped if _target is empty
if ((t = vars["_target"])) {
array(mixed) u;
unless (u = parse_uniform(t)) {
CIRCUITERROR("target is not an uniform\n")
}
// FIXME relaying support here?
unless (is_localhost(u[UHost])) {
CIRCUITERROR("target is not configured on this server\n")
}
}
foreach(vname, vop : varops) {
if (vop[1] == 0) // vname was encountered in mmp header
continue;
switch(vop[0]) { // the glyph
case C_GLYPH_MODIFIER_SET:
vars[vname] = vop[2];
break;
case C_GLYPH_MODIFIER_ASSIGN:
case C_GLYPH_MODIFIER_AUGMENT:
case C_GLYPH_MODIFIER_DIMINISH:
case C_GLYPH_MODIFIER_QUERY:
CIRCUITERROR("stateful operation in udp mode")
break;
}
if (!legal_keyword(vname) || abbrev("_INTERNAL", vname)) {
CIRCUITERROR("illegal varname in psyc")
}
}
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 source, target;
if (vars["_source"] && vars["_target"]) {
P0(("invalid _context %O with _source %O, _target %O\n",
context, vars["_source"], vars["_target"]))
CIRCUITERROR("invalid usage of context with _source and _target");
} else if (vars["_source"]) {
P0(("invalid _context %O with _source %O and empty _target\n",
context, vars["_source"]))
CIRCUITERROR("invalid usage of context with _source");
} else if (vars["_target"]) {
// as we don't have psyc e2e state yet...
P0(("unicast from context %O to target %O not implemented yet\n",
context, vars["_target"]))
CIRCUITERROR("unicast from context to single member not implemented yet");
} 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"]);
}
}
context = find_context(vars["_context"]);
if (objectp(context)) {
// FIXME: we need lots of 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
}
}
} else {
if (!vars["_target"] && !vars["_source"]) {
CIRCUITERROR("circuit_msg over udp?");
} else {
string source;
mixed target;
if (!vars["_source"]) {
source = netloc;
} else {
// FIXME: a macro NORMALIZE_UNIFORM that may do lower_case please
// not a simple lower_case
source = lower_case(vars["_source"]);
}
// FIXME
if (!vars["_target"])
return;
// deliver
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 {
P0(("target %O not found???\n", target))
}
}
}
::done(varops, method, body);
}

4
world/net/spyc/user.c Normal file
View file

@ -0,0 +1,4 @@
// $Id: user.c,v 1.1 2008/01/30 17:06:48 lynx Exp $ // vim:syntax=lpc
#include "psyc.h"
#include "../psyc/user.c"