Merge branch 'master' of feistymeow.org:feisty_meow
[feisty_meow.git] / octopi / library / cromp / cromp_client.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : cromp_client                                                      *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
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 \*****************************************************************************/
14
15 #include "cromp_client.h"
16 #include "cromp_common.h"
17 #include "cromp_transaction.h"
18
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>
44
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;
55
56 namespace cromp {
57
58 //#define DEBUG_CROMP_CLIENT
59   // uncomment for noisier version.
60
61 #undef LOG
62 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
63
64 const int MAX_CONN_ATTEMPTS = 3;
65   // the number of times we'll retry connecting for certain errors.
66
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.
70
71 // grab control of the class, preventing multiple threads from trampling data.
72 #define AUTO_LOCK \
73   auto_synchronizer l(*_lock)
74
75 // make sure the client is in an operational state.
76 #define CHECK_LOCKOUT \
77   if (_disallowed) { \
78     /* we can't do anything now due to the state of the connection. */ \
79     return NO_CONNECTION; \
80   }
81
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); \
85   if (!newvar) { \
86     LOG("failed to cast " #varname " to appropriate type, " #type "."); \
87     WHACK(varname); \
88     return retval; \
89   }
90
91 //////////////
92
93 class asynch_connection_thread : public ethread
94 {
95 public:
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.");
104         break;  // done?
105       }
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.");
114     }
115     // single shot thread is exiting now.
116     _parent._disallowed = false;
117   }
118
119 private:
120   cromp_client &_parent;
121 };
122
123 //////////////
124
125 cromp_client::cromp_client(const internet_address &addr, int connection_wait,
126     int max_per_ent)
127 : cromp_common(cromp_common::chew_hostname(addr), max_per_ent),
128   _encrypting(false),
129   _connection_wait(connection_wait),
130   _lock(new mutex),
131   _ent(new octopus_entity(randomize_entity())),
132   _req_id(new int_roller(1, MAXINT32 - 20)),
133   _identified(false),
134   _authorized(false),
135   _disallowed(false),
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)
142 {
143 #ifdef DEBUG_CROMP_CLIENT
144   FUNCDEF("constructor");
145   LOG(astring("initial entity=") + _ent->mangled_form());
146 #endif
147   open_common(addr);
148
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.
153 }
154
155 cromp_client::~cromp_client()
156 {
157   FUNCDEF("destructor");
158   disconnect();
159   close_common();
160   _identified = false;
161   _authorized = false;
162   WHACK(_ent);
163   WHACK(_req_id);
164   _channel_secured = false;
165   WHACK(_crypto);
166   WHACK(_guardian);
167   WHACK(c_verification);
168   WHACK(_lock);
169 }
170
171 bool cromp_client::connected() const { return spock()->connected(); }
172
173 const byte_array &cromp_client::verification() const
174 { return *c_verification; }
175
176 void cromp_client::enable_encryption()
177 {
178   FUNCDEF("enable_encryption");
179   AUTO_LOCK;
180
181 #ifdef DEBUG_CROMP_CLIENT
182   LOG(astring("enabling encryption for ") + class_name() + " on "
183       + other_side().text_form());
184 #endif
185   _encrypting = true;
186
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
196 //      existed?
197   } else
198     _encrypt_arm = new encryption_tentacle(encryption_infoton::RSA_KEY_SIZE);
199   add_tentacle(_encrypt_arm, true);
200   add_tentacle(new unwrapping_tentacle, false);
201 }
202
203 void cromp_client::stop_asynch_thread()
204 {
205 #ifdef DEBUG_CROMP_CLIENT
206   FUNCDEF("stop_asynch_thread");
207 #endif
208   if (_asynch_connector) {
209 #ifdef DEBUG_CROMP_CLIENT
210     LOG(instance_name() + " stopping thread.");
211 #endif
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);
216   }
217   _disallowed = false;  // no longer running the background thread.
218 }
219
220 void cromp_client::reset(const internet_address &addr, int connection_wait,
221     int max_per_ent)
222 {
223 #ifdef DEBUG_CROMP_CLIENT
224   FUNCDEF("reset");
225 #endif
226   stop_asynch_thread();
227   AUTO_LOCK;
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);
232   _identified = false;
233   _authorized = false;
234   _channel_secured = false;
235   _connection_wait = connection_wait;
236   _disallowed = false;
237 #ifdef DEBUG_CROMP_CLIENT
238   LOG(astring("resetting entity=") + _ent->mangled_form());
239 #endif
240   open_common(addr);
241 }
242
243 const octopus_entity &cromp_client::entity() const
244 {
245   AUTO_LOCK;
246   return *_ent;
247 }
248
249 SAFE_STATIC(tcpip_stack, _hidden_stack, )
250
251 octopus_entity cromp_client::randomize_entity() const
252 {
253   astring host = cromp_common::chew_hostname(internet_address
254       (byte_array::empty_array(), _hidden_stack().hostname(), 0), NULL_POINTER);
255   chaos randomizer;
256   return octopus_entity(host, application_configuration::process_id(),
257       randomizer.inclusive(0, MAXINT32 / 3),
258       randomizer.inclusive(0, MAXINT32 / 3));
259 }
260
261 octopus_request_id cromp_client::next_id()
262 {
263   AUTO_LOCK;
264   return octopus_request_id(*_ent, _req_id->next_id());
265 }
266
267 outcome cromp_client::synchronous_request(const infoton &to_send,
268     infoton * & received, octopus_request_id &item_id,
269     int timeout)
270 {
271   FUNCDEF("synchronous_request");
272   received = NULL_POINTER;
273   outcome ret = submit(to_send, item_id);
274   if (ret != OKAY) {
275     LOG(astring("failed to submit request: ") + outcome_name(ret) + " on " + to_send.text_form());
276     return ret;
277   }
278   ret = acquire(received, item_id, timeout);
279   if (ret != OKAY) {
280     LOG(astring("failed to acquire response: ") + outcome_name(ret) + " for " + to_send.text_form());
281     return ret;
282   }
283   return OKAY;
284 }
285
286 SAFE_STATIC(byte_array, _empty_blank_verif, );
287 const byte_array &cromp_client::blank_verification()
288 { return _empty_blank_verif(); }
289
290 outcome cromp_client::login()
291 {
292   FUNCDEF("login");
293   CHECK_LOCKOUT;
294   if (!_identified) {
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();
299     infoton *response;
300     outcome ret = synchronous_request(identity, response, item_id);
301     if (ret != OKAY) return ret;
302
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());
308 #endif
309       AUTO_LOCK;
310       *_ent = ide_reply->_new_name;
311       _identified = true;
312     } else {
313 #ifdef DEBUG_CROMP_CLIENT
314       LOG("identity request failed: got blank name.");
315 #endif
316     }
317     WHACK(ide_reply);
318   }
319
320   if (_encrypting && !_channel_secured) {
321     // now the encryption needs to be cranked up.
322
323     if (!_encrypt_arm)
324       LOG("there's no encryption arm!!!!");
325
326     encryption_infoton encro;
327     {
328       AUTO_LOCK;
329       encro.prepare_public_key(_encrypt_arm->private_key());
330     }
331
332     infoton *response;
333     octopus_request_id item_id;
334     outcome ret = synchronous_request(encro, response, item_id);
335     if (ret != OKAY) return ret;
336
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.
341
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);
345     if (ret != OKAY) {
346       LOG(astring("failed to process encryption infoton for ")
347           + item_id.text_form());
348       WHACK(enc_reply);  // nothing to give out.
349       return ret;
350     }
351     WHACK(enc_reply);
352
353     octenc_key_record *reco = _encrypt_arm->keys().lock(item_id._entity);
354     if (!reco) {
355       LOG(astring("failed to locate key for ") + item_id._entity.text_form());
356       return NOT_FOUND;
357     }
358     _crypto->set_key(reco->_key.get_key(),
359         encryption_infoton::BLOWFISH_KEY_SIZE);
360     _encrypt_arm->keys().unlock(reco);
361     _channel_secured = true;
362   }
363
364   if (!_authorized) {
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;
369     infoton *response;
370     outcome ret = synchronous_request(securinfo, response, item_id);
371     unhandled_request *temp_unh = dynamic_cast<unhandled_request *>(response);
372     if (temp_unh) {
373 #ifdef DEBUG_CROMP_CLIENT
374       LOG(astring("got an unhandled request with reason: ")
375           + common::outcome_name(temp_unh->_reason));
376 #endif
377       return temp_unh->_reason;  // return the original reason.
378     }
379     CAST_REPLY(security_infoton, response, sec_reply, NO_SERVER);
380     outcome success = sec_reply->_success;
381     WHACK(sec_reply);
382     if (success == tentacle::OKAY) {
383       AUTO_LOCK;
384       _authorized = true;
385 #ifdef DEBUG_CROMP_CLIENT
386       LOG(astring("login request succeeded, now logged in."));
387 #endif
388     } else {
389 #ifdef DEBUG_CROMP_CLIENT
390       LOG(astring("login request failed."));
391 #endif
392       return success;
393     }
394   }
395
396   return OKAY;
397 }
398
399 outcome cromp_client::connect(const byte_array &verification)
400 {
401   FUNCDEF("connect");
402   stop_asynch_thread();
403   AUTO_LOCK;  // protect from multiple connect attempts.
404   *c_verification = verification;
405   return locked_connect();
406 }
407
408 outcome cromp_client::asynch_connect()
409 {
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.");
415 //#endif
416   {
417     AUTO_LOCK;
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;
423     }
424     _disallowed = true;
425     _asynch_connector = new asynch_connection_thread(*this);
426   }
427   _asynch_connector->start(NULL_POINTER);
428 //#ifdef DEBUG_CROMP_CLIENT
429   LOG(instance_name() + " exit.");
430 //#endif
431   return NO_CONNECTION;
432 }
433
434 outcome cromp_client::locked_connect()
435 {
436   FUNCDEF("locked_connect");
437   if (!spock()) return BAD_INPUT;
438   if (connected()) return OKAY;  // already connected.
439
440   locked_disconnect();  // clean out any previous connection.
441   *_ent = randomize_entity();  // reset the login id.
442
443   int attempts = 0;
444   while (attempts++ < MAX_CONN_ATTEMPTS) {
445 #ifdef DEBUG_CROMP_CLIENT
446     LOG(instance_name() + " calling spocket connect.");
447 #endif
448     outcome ret = spock()->connect(_connection_wait);
449 #ifdef DEBUG_CROMP_CLIENT
450     LOG(instance_name() + " done calling spocket connect.");
451 #endif
452     if (ret == spocket::OKAY) {
453 #ifdef DEBUG_CROMP_CLIENT
454       LOG("finished connection...  now affirming identity.");
455 #endif
456       return login();
457     }
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.
461       locked_disconnect();
462       return NO_SERVER;
463     }
464 #ifdef DEBUG_CROMP_CLIENT
465     LOG(a_sprintf("error gotten=%s", spocket::outcome_name(ret)));
466 #endif
467
468     if (attempts < MAX_CONN_ATTEMPTS - 1)
469       time_control::sleep_ms(INTERCONNECTION_SNOOZE);
470   }
471   LOG(instance_name() + " failed to connect.");
472   locked_disconnect();  // clean up.
473   return NO_CONNECTION;
474 }
475
476 outcome cromp_client::disconnect()
477 {
478   stop_asynch_thread();
479   AUTO_LOCK;
480   return locked_disconnect();
481 }
482
483 void cromp_client::keep_alive_pause(int duration, int interval)
484 {
485   if (duration < 0) duration = 0;
486   if (interval < 0) interval = 40;
487   if (interval > duration) interval = duration;
488   
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) {
492     push_outgoing(1);
493     grab_anything(false);
494     // we'll only sleep if they didn't give us a zero duration.
495     if (duration)
496       time_control::sleep_ms(interval);  // snooze a hopefully short time.
497   }
498 }
499
500 outcome cromp_client::locked_disconnect()
501 {
502   if (!spock()) return BAD_INPUT;
503   outcome ret = spock()->disconnect();
504   _identified = false;
505   _authorized = false;
506   _channel_secured = false;
507   *_ent = octopus_entity();  // reset the login id.
508   if (ret != spocket::OKAY) {
509 //hmmm: any other outcomes to return?
510     return OKAY;
511   }
512   return OKAY;
513 }
514
515 bool cromp_client::wrap_infoton(const infoton &request,
516     encryption_wrapper &wrapped)
517 {
518 #ifdef DEBUG_CROMP_CLIENT
519   FUNCDEF("wrap_infoton");
520 #endif
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());
534 #endif
535     byte_array packed_request;
536     infoton::fast_pack(packed_request, request);
537     _crypto->encrypt(packed_request, wrapped._wrapped);
538     return true;
539   } else return false;  // we didn't need or want to wrap it.
540 }
541
542 outcome cromp_client::submit(const infoton &request,
543     octopus_request_id &item_id, int max_tries)
544 {
545 #ifdef DEBUG_CROMP_CLIENT
546   FUNCDEF("submit");
547 #endif
548   CHECK_LOCKOUT;
549   item_id = next_id();
550   bool is_ident = !!dynamic_cast<const identity_infoton *>(&request);
551   if (!_identified && !is_ident) return BAD_INPUT;
552
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.
560
561     encryption_wrapper real_request;
562     bool wrapped_okay = wrap_infoton(request, real_request);
563     if (wrapped_okay) {
564       outcome to_return = cromp_common::pack_and_ship(real_request, item_id,
565           max_tries);
566       return to_return;
567     }
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.");
574 #endif
575   }
576
577   outcome to_return = cromp_common::pack_and_ship(request, item_id, max_tries);
578   return to_return;
579 }
580
581 outcome cromp_client::acquire(infoton * &response,
582     const octopus_request_id &cmd_id, int timeout)
583 {
584   FUNCDEF("acquire");
585   CHECK_LOCKOUT;
586   outcome to_return = cromp_common::retrieve_and_restore(response, cmd_id,
587       timeout);
588
589   unhandled_request *intermed = dynamic_cast<unhandled_request *>(response);
590   if (intermed) {
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));
594   }
595
596   decrypt_package_as_needed(to_return, response, cmd_id);
597
598   return to_return;
599 }
600
601 outcome cromp_client::acquire_any(infoton * &response,
602     octopus_request_id &cmd_id, int timeout)
603 {
604 #ifdef DEBUG_CROMP_CLIENT
605   FUNCDEF("acquire_any");
606 #endif
607   CHECK_LOCKOUT;
608   outcome to_return = cromp_common::retrieve_and_restore_any(response, cmd_id,
609       timeout);
610
611   unhandled_request *intermed = dynamic_cast<unhandled_request *>(response);
612   if (intermed) {
613     // override the return value with the real outcome of a failed operation.
614     to_return = intermed->_reason;
615   }
616
617   decrypt_package_as_needed(to_return, response, cmd_id);
618
619   return to_return;
620 }
621
622 void cromp_client::decrypt_package_as_needed(outcome &to_return,
623     infoton * &response, const octopus_request_id &cmd_id)
624 {
625   FUNCDEF("decrypt_package_as_needed");
626   if (dynamic_cast<encryption_wrapper *>(response)) {
627     if (!_encrypt_arm) {
628       LOG(astring("received an encryption_wrapper but we are not "
629           "encrypting, on ") + cmd_id.text_form());
630       to_return = ENCRYPTION_MISMATCH;
631       return;
632     }
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());
637       to_return = ret;
638       return;
639     }
640
641     string_array classif;
642     byte_array decro;  // decrypted packed infoton.
643     bool worked = infoton::fast_unpack(transformed, classif, decro);
644     if (!worked) {
645       LOG("failed to fast_unpack the transformed data.");
646       to_return = ENCRYPTION_MISMATCH;  // what else would we call that?
647     } else {
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.
652         WHACK(response);
653         response = new_req;  // substitution complete.
654       } else {
655         LOG("failed to restore transformed infoton.");
656         to_return = ENCRYPTION_MISMATCH;  // what else would we call that?
657       }
658     }
659   }
660 }
661
662 } //namespace.
663
664