1 /*****************************************************************************\
3 * Name : cromp_client *
4 * Author : Chris Koeritz *
6 *******************************************************************************
7 * Copyright (c) 2000-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 \*****************************************************************************/
15 #include "cromp_client.h"
16 #include "cromp_common.h"
17 #include "cromp_transaction.h"
19 #include <basis/astring.h>
20 #include <basis/functions.h>
21 #include <basis/mutex.h>
22 #include <configuration/application_configuration.h>
23 #include <crypto/rsa_crypto.h>
24 #include <loggers/program_wide_logger.h>
25 #include <mathematics/chaos.h>
26 #include <octopus/entity_defs.h>
27 #include <octopus/identity_infoton.h>
28 #include <octopus/unhandled_request.h>
29 #include <processes/ethread.h>
30 #include <sockets/internet_address.h>
31 #include <sockets/machine_uid.h>
32 #include <sockets/spocket.h>
33 #include <sockets/tcpip_stack.h>
34 #include <structures/static_memory_gremlin.h>
35 #include <structures/roller.h>
36 #include <tentacles/encryption_tentacle.h>
37 #include <tentacles/encryption_wrapper.h>
38 #include <tentacles/entity_registry.h>
39 #include <tentacles/key_repository.h>
40 #include <tentacles/login_tentacle.h>
41 #include <tentacles/security_infoton.h>
42 #include <timely/time_control.h>
43 #include <timely/time_stamp.h>
45 using namespace basis;
46 using namespace configuration;
47 using namespace crypto;
48 using namespace loggers;
49 using namespace mathematics;
50 using namespace octopi;
51 using namespace processes;
52 using namespace sockets;
53 using namespace structures;
54 using namespace timely;
58 //#define DEBUG_CROMP_CLIENT
59 // uncomment for noisier version.
62 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
64 const int MAX_CONN_ATTEMPTS = 3;
65 // the number of times we'll retry connecting for certain errors.
67 const int INTERCONNECTION_SNOOZE = 200;
68 // we will pause this long if the initial connection attempt failed, and
69 // in between each attempt thereafter except the last.
71 // grab control of the class, preventing multiple threads from trampling data.
73 auto_synchronizer l(*_lock)
75 // make sure the client is in an operational state.
76 #define CHECK_LOCKOUT \
78 /* we can't do anything now due to the state of the connection. */ \
79 return NO_CONNECTION; \
82 // tries to get a particular type of object back from an infoton response.
83 #define CAST_REPLY(type, varname, newvar, retval) \
84 type *newvar = dynamic_cast<type *>(varname); \
86 LOG("failed to cast " #varname " to appropriate type, " #type "."); \
93 class asynch_connection_thread : public ethread
96 asynch_connection_thread(cromp_client &parent)
97 : ethread(), _parent(parent) {}
98 ~asynch_connection_thread() { stop(); }
99 void perform_activity(void *formal(ptr)) {
100 FUNCDEF("perform_activity");
101 while (!should_stop()) {
102 if (_parent.connected()) {
103 LOG(_parent.instance_name() + " got connected.");
106 // invoke the real connection maker. we should be synchronized wrt
107 // multiple threads since the "_disallowed" flag is set before this
108 // thread is ever started. no one that locks the cromp_client will
109 // get a chance to interfere.
110 LOG(_parent.instance_name() + " still unconnected; trying connect now.");
111 _parent.locked_connect();
112 LOG(_parent.instance_name()
113 + " done calling connect.");
115 // single shot thread is exiting now.
116 _parent._disallowed = false;
120 cromp_client &_parent;
125 cromp_client::cromp_client(const internet_address &addr, int connection_wait,
127 : cromp_common(cromp_common::chew_hostname(addr), max_per_ent),
129 _connection_wait(connection_wait),
131 _ent(new octopus_entity(randomize_entity())),
132 _req_id(new int_roller(1, MAXINT32 - 20)),
136 _asynch_connector(NULL_POINTER),
137 _channel_secured(false),
138 _crypto(new blowfish_crypto(encryption_infoton::BLOWFISH_KEY_SIZE)),
139 _encrypt_arm(NULL_POINTER),
140 _guardian(new blank_entity_registry),
141 c_verification(new byte_array)
143 #ifdef DEBUG_CROMP_CLIENT
144 FUNCDEF("constructor");
145 LOG(astring("initial entity=") + _ent->mangled_form());
149 // add simple security handling.
150 add_tentacle(new login_tentacle(*_guardian));
151 // add a non-filtering tentacle for checking security. we mainly need
152 // this to be able to unpack answers from the server.
155 cromp_client::~cromp_client()
157 FUNCDEF("destructor");
164 _channel_secured = false;
167 WHACK(c_verification);
171 bool cromp_client::connected() const { return spock()->connected(); }
173 const byte_array &cromp_client::verification() const
174 { return *c_verification; }
176 void cromp_client::enable_encryption()
178 FUNCDEF("enable_encryption");
181 #ifdef DEBUG_CROMP_CLIENT
182 LOG(astring("enabling encryption for ") + class_name() + " on "
183 + other_side().text_form());
187 // plug in the encryption support.
188 if (other_side().is_localhost()) {
189 // if the address is localhost, then we will use the standard key.
190 byte_array temp_priv_key;
191 localhost_only_key().private_key(temp_priv_key);
192 _encrypt_arm = new encryption_tentacle(temp_priv_key);
193 //hmmm: there is a risk that if they reset to a new address we'd still be
194 // using the slightly less secure local key. could be ameliorated by
195 // zapping the encryption tentacle for a reset and readding it if it
198 _encrypt_arm = new encryption_tentacle(encryption_infoton::RSA_KEY_SIZE);
199 add_tentacle(_encrypt_arm, true);
200 add_tentacle(new unwrapping_tentacle, false);
203 void cromp_client::stop_asynch_thread()
205 #ifdef DEBUG_CROMP_CLIENT
206 FUNCDEF("stop_asynch_thread");
208 if (_asynch_connector) {
209 #ifdef DEBUG_CROMP_CLIENT
210 LOG(instance_name() + " stopping thread.");
212 _asynch_connector->cancel(); // send it a nudge before we grab control.
213 AUTO_LOCK; // lock the class to prevent interference.
214 _asynch_connector->stop();
215 WHACK(_asynch_connector);
217 _disallowed = false; // no longer running the background thread.
220 void cromp_client::reset(const internet_address &addr, int connection_wait,
223 #ifdef DEBUG_CROMP_CLIENT
226 stop_asynch_thread();
228 close_common(); // shut down the low-level stuff.
229 max_bytes_per_entity(max_per_ent);
230 *_ent = randomize_entity();
231 _req_id->set_current(1);
234 _channel_secured = false;
235 _connection_wait = connection_wait;
237 #ifdef DEBUG_CROMP_CLIENT
238 LOG(astring("resetting entity=") + _ent->mangled_form());
243 const octopus_entity &cromp_client::entity() const
249 SAFE_STATIC(tcpip_stack, _hidden_stack, )
251 octopus_entity cromp_client::randomize_entity() const
253 astring host = cromp_common::chew_hostname(internet_address
254 (byte_array::empty_array(), _hidden_stack().hostname(), 0), NULL_POINTER);
256 return octopus_entity(host, application_configuration::process_id(),
257 randomizer.inclusive(0, MAXINT32 / 3),
258 randomizer.inclusive(0, MAXINT32 / 3));
261 octopus_request_id cromp_client::next_id()
264 return octopus_request_id(*_ent, _req_id->next_id());
267 outcome cromp_client::synchronous_request(const infoton &to_send,
268 infoton * & received, octopus_request_id &item_id,
271 FUNCDEF("synchronous_request");
272 received = NULL_POINTER;
273 outcome ret = submit(to_send, item_id);
275 LOG(astring("failed to submit request: ") + outcome_name(ret) + " on " + to_send.text_form());
278 ret = acquire(received, item_id, timeout);
280 LOG(astring("failed to acquire response: ") + outcome_name(ret) + " for " + to_send.text_form());
286 SAFE_STATIC(byte_array, _empty_blank_verif, );
287 const byte_array &cromp_client::blank_verification()
288 { return _empty_blank_verif(); }
290 outcome cromp_client::login()
295 _channel_secured = false;
296 // we need to secure an identity with the server.
297 identity_infoton identity;
298 octopus_request_id item_id = octopus_request_id::randomized_id();
300 outcome ret = synchronous_request(identity, response, item_id);
301 if (ret != OKAY) return ret;
303 CAST_REPLY(identity_infoton, response, ide_reply, NO_SERVER);
304 if (!ide_reply->_new_name.blank()) {
305 #ifdef DEBUG_CROMP_CLIENT
306 LOG(astring("setting new entity to: ")
307 + ide_reply->_new_name.mangled_form());
310 *_ent = ide_reply->_new_name;
313 #ifdef DEBUG_CROMP_CLIENT
314 LOG("identity request failed: got blank name.");
320 if (_encrypting && !_channel_secured) {
321 // now the encryption needs to be cranked up.
324 LOG("there's no encryption arm!!!!");
326 encryption_infoton encro;
329 encro.prepare_public_key(_encrypt_arm->private_key());
333 octopus_request_id item_id;
334 outcome ret = synchronous_request(encro, response, item_id);
335 if (ret != OKAY) return ret;
337 CAST_REPLY(encryption_infoton, response, enc_reply, ENCRYPTION_MISMATCH);
338 // this is a reasonable answer (mismatch), because a non-encrypting
339 // server should tell us a general failure response, since it shouldn't
340 // understand the request.
342 // handle the encryption infoton by feeding our tentacle the new key.
343 byte_array transformed;
344 ret = _encrypt_arm->consume(*enc_reply, item_id, transformed);
346 LOG(astring("failed to process encryption infoton for ")
347 + item_id.text_form());
348 WHACK(enc_reply); // nothing to give out.
353 octenc_key_record *reco = _encrypt_arm->keys().lock(item_id._entity);
355 LOG(astring("failed to locate key for ") + item_id._entity.text_form());
358 _crypto->set_key(reco->_key.get_key(),
359 encryption_infoton::BLOWFISH_KEY_SIZE);
360 _encrypt_arm->keys().unlock(reco);
361 _channel_secured = true;
365 // we need to go through whatever authentication is used by the server.
366 security_infoton::login_modes login_type = security_infoton::LI_LOGIN;
367 security_infoton securinfo(login_type, OKAY, *c_verification);
368 octopus_request_id item_id;
370 outcome ret = synchronous_request(securinfo, response, item_id);
371 unhandled_request *temp_unh = dynamic_cast<unhandled_request *>(response);
373 #ifdef DEBUG_CROMP_CLIENT
374 LOG(astring("got an unhandled request with reason: ")
375 + common::outcome_name(temp_unh->_reason));
377 return temp_unh->_reason; // return the original reason.
379 CAST_REPLY(security_infoton, response, sec_reply, NO_SERVER);
380 outcome success = sec_reply->_success;
382 if (success == tentacle::OKAY) {
385 #ifdef DEBUG_CROMP_CLIENT
386 LOG(astring("login request succeeded, now logged in."));
389 #ifdef DEBUG_CROMP_CLIENT
390 LOG(astring("login request failed."));
399 outcome cromp_client::connect(const byte_array &verification)
402 stop_asynch_thread();
403 AUTO_LOCK; // protect from multiple connect attempts.
404 *c_verification = verification;
405 return locked_connect();
408 outcome cromp_client::asynch_connect()
410 FUNCDEF("asynch_connect");
411 if (connected()) return OKAY; // why bother?
412 if (_asynch_connector) return NO_CONNECTION; // in progress.
413 //#ifdef DEBUG_CROMP_CLIENT
414 LOG(instance_name() + " entry.");
418 // protect this block only; we want to unlock before thread gets started.
419 if (connected()) return OKAY; // done already somehow.
420 if (_asynch_connector) {
421 LOG("logic error: asynchronous connector already exists.");
422 return NO_CONNECTION;
425 _asynch_connector = new asynch_connection_thread(*this);
427 _asynch_connector->start(NULL_POINTER);
428 //#ifdef DEBUG_CROMP_CLIENT
429 LOG(instance_name() + " exit.");
431 return NO_CONNECTION;
434 outcome cromp_client::locked_connect()
436 FUNCDEF("locked_connect");
437 if (!spock()) return BAD_INPUT;
438 if (connected()) return OKAY; // already connected.
440 locked_disconnect(); // clean out any previous connection.
441 *_ent = randomize_entity(); // reset the login id.
444 while (attempts++ < MAX_CONN_ATTEMPTS) {
445 #ifdef DEBUG_CROMP_CLIENT
446 LOG(instance_name() + " calling spocket connect.");
448 outcome ret = spock()->connect(_connection_wait);
449 #ifdef DEBUG_CROMP_CLIENT
450 LOG(instance_name() + " done calling spocket connect.");
452 if (ret == spocket::OKAY) {
453 #ifdef DEBUG_CROMP_CLIENT
454 LOG("finished connection... now affirming identity.");
458 if (ret == spocket::TIMED_OUT) return TIMED_OUT;
459 if ( (ret == spocket::NO_ANSWER) || (ret == spocket::ACCESS_DENIED) ) {
460 // clean up. this is a real case of something hosed.
464 #ifdef DEBUG_CROMP_CLIENT
465 LOG(a_sprintf("error gotten=%s", spocket::outcome_name(ret)));
468 if (attempts < MAX_CONN_ATTEMPTS - 1)
469 time_control::sleep_ms(INTERCONNECTION_SNOOZE);
471 LOG(instance_name() + " failed to connect.");
472 locked_disconnect(); // clean up.
473 return NO_CONNECTION;
476 outcome cromp_client::disconnect()
478 stop_asynch_thread();
480 return locked_disconnect();
483 void cromp_client::keep_alive_pause(int duration, int interval)
485 if (duration < 0) duration = 0;
486 if (interval < 0) interval = 40;
487 if (interval > duration) interval = duration;
489 // keep looping on the cromp stimulation methods until the time has elapsed.
490 time_stamp leave_at(duration);
491 while (time_stamp() < leave_at) {
493 grab_anything(false);
494 // we'll only sleep if they didn't give us a zero duration.
496 time_control::sleep_ms(interval); // snooze a hopefully short time.
500 outcome cromp_client::locked_disconnect()
502 if (!spock()) return BAD_INPUT;
503 outcome ret = spock()->disconnect();
506 _channel_secured = false;
507 *_ent = octopus_entity(); // reset the login id.
508 if (ret != spocket::OKAY) {
509 //hmmm: any other outcomes to return?
515 bool cromp_client::wrap_infoton(const infoton &request,
516 encryption_wrapper &wrapped)
518 #ifdef DEBUG_CROMP_CLIENT
519 FUNCDEF("wrap_infoton");
521 if (!_channel_secured) return false;
522 // identity is not wrapped with encryption; we need to establish and identity
523 // to talk on a distinct channel with the server. even if that identity were
524 // compromised, the interloper should still not be able to listen in on the
525 // establishment of an encryption channel.
526 bool is_ident = !!dynamic_cast<const identity_infoton *>(&request);
527 bool is_encrypt = !!dynamic_cast<const encryption_infoton *>(&request);
528 bool is_wrapper = !!dynamic_cast<const encryption_wrapper *>(&request);
529 if (!is_ident && !is_encrypt && !is_wrapper) {
530 // check that we have already got a channel to speak over. otherwise, we
531 // can't do any encrypting of messages yet.
532 #ifdef DEBUG_CROMP_CLIENT
533 LOG(astring("encrypting ") + request.text_form());
535 byte_array packed_request;
536 infoton::fast_pack(packed_request, request);
537 _crypto->encrypt(packed_request, wrapped._wrapped);
539 } else return false; // we didn't need or want to wrap it.
542 outcome cromp_client::submit(const infoton &request,
543 octopus_request_id &item_id, int max_tries)
545 #ifdef DEBUG_CROMP_CLIENT
550 bool is_ident = !!dynamic_cast<const identity_infoton *>(&request);
551 if (!_identified && !is_ident) return BAD_INPUT;
553 if (_encrypting && _channel_secured) {
554 // if we're encrypting things, then we need to encrypt this too. this
555 // assumes that authentication is wrapped by encryption, which is the sane
556 // thing to do. identity is not wrapped that way though; we need to
557 // establish and identity to talk on a distinct channel with the server.
558 // even if that identity were compromised, the interloper would still not
559 // be able to listen in on the establishment of an encryption channel.
561 encryption_wrapper real_request;
562 bool wrapped_okay = wrap_infoton(request, real_request);
564 outcome to_return = cromp_common::pack_and_ship(real_request, item_id,
568 // if it didn't wrap okay, we fall through to a normal send, because it's
569 // probably an encryption or identity infoton, which needs to go through
570 // without being wrapped.
571 } else if (_encrypting) {
572 #ifdef DEBUG_CROMP_CLIENT
573 LOG("the channel has not been secured yet.");
577 outcome to_return = cromp_common::pack_and_ship(request, item_id, max_tries);
581 outcome cromp_client::acquire(infoton * &response,
582 const octopus_request_id &cmd_id, int timeout)
586 outcome to_return = cromp_common::retrieve_and_restore(response, cmd_id,
589 unhandled_request *intermed = dynamic_cast<unhandled_request *>(response);
591 // override the return value with the real outcome of a failed operation.
592 to_return = intermed->_reason;
593 LOG(astring("using unhandled request's intermediate result: ") + outcome_name(to_return));
596 decrypt_package_as_needed(to_return, response, cmd_id);
601 outcome cromp_client::acquire_any(infoton * &response,
602 octopus_request_id &cmd_id, int timeout)
604 #ifdef DEBUG_CROMP_CLIENT
605 FUNCDEF("acquire_any");
608 outcome to_return = cromp_common::retrieve_and_restore_any(response, cmd_id,
611 unhandled_request *intermed = dynamic_cast<unhandled_request *>(response);
613 // override the return value with the real outcome of a failed operation.
614 to_return = intermed->_reason;
617 decrypt_package_as_needed(to_return, response, cmd_id);
622 void cromp_client::decrypt_package_as_needed(outcome &to_return,
623 infoton * &response, const octopus_request_id &cmd_id)
625 FUNCDEF("decrypt_package_as_needed");
626 if (dynamic_cast<encryption_wrapper *>(response)) {
628 LOG(astring("received an encryption_wrapper but we are not "
629 "encrypting, on ") + cmd_id.text_form());
630 to_return = ENCRYPTION_MISMATCH;
633 byte_array transformed;
634 outcome ret = _encrypt_arm->consume(*response, cmd_id, transformed);
635 if ( (ret != OKAY) && (ret != PARTIAL) ) {
636 LOG(astring("failed to decrypt wrapper for ") + cmd_id.text_form());
641 string_array classif;
642 byte_array decro; // decrypted packed infoton.
643 bool worked = infoton::fast_unpack(transformed, classif, decro);
645 LOG("failed to fast_unpack the transformed data.");
646 to_return = ENCRYPTION_MISMATCH; // what else would we call that?
648 infoton *new_req = NULL_POINTER;
649 outcome rest_ret = octo()->restore(classif, decro, new_req);
650 if (rest_ret == tentacle::OKAY) {
651 // we got a good transformed version.
653 response = new_req; // substitution complete.
655 LOG("failed to restore transformed infoton.");
656 to_return = ENCRYPTION_MISMATCH; // what else would we call that?