File Coverage

blib/lib/Net/Clacks/Server.pm
Criterion Covered Total %
statement 88 1069 8.2
branch 0 418 0.0
condition 0 166 0.0
subroutine 29 60 48.3
pod 3 3 100.0
total 120 1716 6.9


line stmt bran cond sub pod time code
1             package Net::Clacks::Server;
2             #---AUTOPRAGMASTART---
3 1     1   28 use 5.020;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         25  
5 1     1   4 use warnings;
  1         2  
  1         35  
6 1     1   5 use diagnostics;
  1         1  
  1         10  
7 1     1   33 use mro 'c3';
  1         2  
  1         9  
8 1     1   24 use English;
  1         2  
  1         7  
9 1     1   424 use Carp;
  1         2  
  1         79  
10             our $VERSION = 24;
11 1     1   6 use autodie qw( close );
  1         2  
  1         8  
12 1     1   383 use Array::Contains;
  1         2  
  1         48  
13 1     1   5 use utf8;
  1         2  
  1         15  
14 1     1   29 use Encode qw(is_utf8 encode_utf8 decode_utf8);
  1         2  
  1         56  
15 1     1   6 use feature 'signatures';
  1         2  
  1         111  
16 1     1   6 no warnings qw(experimental::signatures);
  1         2  
  1         42  
17             #---AUTOPRAGMAEND---
18              
19 1     1   885 use XML::Simple;
  1         7986  
  1         11  
20 1     1   196 use Time::HiRes qw(sleep usleep time);
  1         2  
  1         13  
21 1     1   176 use Sys::Hostname;
  1         2  
  1         39  
22 1     1   6 use Errno;
  1         2  
  1         30  
23 1     1   5 use IO::Socket::IP;
  1         2  
  1         13  
24 1     1   545 use IO::Select;
  1         2  
  1         32  
25 1     1   5 use IO::Socket::SSL;
  1         4  
  1         10  
26 1     1   789 use YAML::Syck;
  1         2018  
  1         78  
27 1     1   10 use MIME::Base64;
  1         4  
  1         47  
28 1     1   603 use File::Copy;
  1         4447  
  1         85  
29 1     1   578 use Data::Dumper;
  1         5984  
  1         115  
30 1     1   12 use Scalar::Util qw(looks_like_number);
  1         2  
  1         45  
31              
32             # For turning off SSL session cache
33 1     1   764 use Readonly;
  1         3566  
  1         138  
34             Readonly my $SSL_SESS_CACHE_OFF => 0x0000;
35              
36             my %overheadflags = (
37             A => "auth_token", # Authentication token
38             O => "auth_ok", # Authentication OK
39             F => "auth_failed", # Authentication FAILED
40              
41             E => 'error_message', # Server to client error message
42              
43             C => "close_all_connections",
44             D => "discard_message",
45             G => "forward_message",
46             I => "set_interclacks_mode", # value: true/false, disables 'G' and 'U'
47             L => "lock_for_sync", # value: true/false, only available in interclacks client mode
48             M => "informal_message", # informal message, no further operation on it
49             N => "no_logging",
50             S => "shutdown_service", # value: positive number (number in seconds before shutdown). If interclacks clients are present, should be high
51             # enough to flush all buffers to them
52              
53             T => 'timestamp', # Used before KEYSYNC to compensate for time drift between different systems
54             U => "return_to_sender",
55             Z => "no_flags", # Only sent when no other flags are set
56             );
57              
58             BEGIN {
59             {
60             # We need to add some extra function to IO::Socket::SSL so we can track the client ID
61             # on both TCP and Unix Domain Sockets
62 1     1   9 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  1     1   3  
  1         194  
  1         4  
63 1         12 *{"IO::Socket::SSL::_setClientID"} = sub {
64 0     0   0 my ($self, $cid) = @_;
65            
66 0         0 ${*$self}{'__client_id'} = $cid; ## no critic (References::ProhibitDoubleSigils)
  0         0  
67 0         0 return;
68 1         4 };
69            
70 1         3985 *{"IO::Socket::SSL::_getClientID"} = sub {
71 0     0   0 my ($self) = @_;
72            
73 0   0     0 return ${*$self}{'__client_id'} || ''; ## no critic (References::ProhibitDoubleSigils)
74 1         3 };
75              
76             }
77            
78             }
79              
80 0     0 1   sub new($class, $isDebugging, $configfile) {
  0            
  0            
  0            
  0            
81              
82 0           my $self = bless {}, $class;
83              
84 0           $self->{isDebugging} = $isDebugging;
85 0           $self->{configfile} = $configfile;
86              
87 0           $self->{timeoffset} = 0;
88              
89 0 0         if(defined($ENV{CLACKS_SIMULATED_TIME_OFFSET})) {
90 0           $self->{timeoffset} = 0 + $ENV{CLACKS_SIMULATED_TIME_OFFSET};
91 0           print "****** RUNNING WITH A SIMULATED TIME OFFSET OF ", $self->{timeoffset}, " seconds ******\n";
92             }
93              
94 0           $self->{cache} = {};
95              
96 0           return $self;
97             }
98              
99 0     0 1   sub init($self) {
  0            
  0            
100             # Dummy function for backward compatibility
101 0           warn("Deprecated call to init(), you can remove that function from your code");
102 0           return;
103             }
104              
105              
106 0     0 1   sub run($self) { ## no critic (Subroutines::ProhibitExcessComplexity)
  0            
  0            
107 0 0 0       if(!defined($self->{initHasRun}) || !$self->{initHasRun}) {
108 0           $self->_init();
109             }
110              
111             # Let STDOUT/STDERR settle down first
112 0           sleep(0.1);
113              
114             # Need to ignore SIGPIPE, this can screw us over in certain circumstances
115             # while writing to the network. We can only detect certain types of disconnects
116             # after writing to the socket, but we will get a SIGPIPE if we try. So we just
117             # ignore the signal and carry on as usual...
118 0           $SIG{PIPE} = 'IGNORE';
119              
120              
121 0     0     $SIG{INT} = sub { $self->{keepRunning} = 0; };
  0            
122 0     0     $SIG{TERM} = sub { $self->{keepRunning} = 0; };
  0            
123              
124             # Restore persistance file if required
125 0 0         if($self->{persistance}) {
126 0           $self->_restorePersistanceFile();
127             }
128              
129 0           while($self->{keepRunning}) {
130 0           $self->{workCount} = 0;
131              
132             # Check for shutdown time
133 0 0 0       if($self->{shutdowntime} && $self->{shutdowntime} < time) {
134 0           print STDERR "Shutdown time has arrived!\n";
135 0           $self->{keepRunning} = 0;
136             }
137              
138 0           my $now = $self->_getTime();
139 0 0 0       if($self->{savecache} && $now > ($self->{lastsavecache} + $self->{persistanceinterval})) {
140 0           $self->{lastsavecache} = $now;
141 0           $self->_savePersistanceFile();
142 0           $self->{savecache} = 0;
143             }
144              
145             # We are in client mode. We need to add an interclacks link
146 0 0 0       if(defined($self->{config}->{master}->{socket}) || defined($self->{config}->{master}->{ip})) {
147 0           $self->_addInterclacksLink();
148             }
149              
150 0           $self->_addNewClients();
151              
152 0           $self->_disconnectClients();
153              
154 0 0         if(!(scalar keys %{$self->{clients}})) {
  0            
155             # No clients to handle, let's sleep and try again later
156 0           sleep(0.1);
157 0           next;
158             }
159              
160 0           $self->_clientInput();
161              
162              
163 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
164 0           while(@{$self->{clients}->{$cid}->{charbuffer}}) {
  0            
165 0           my $buf = shift @{$self->{clients}->{$cid}->{charbuffer}};
  0            
166              
167 0           $self->{workCount}++;
168 0 0         if($buf eq "\r") {
    0          
169 0           next;
170             } elsif($buf eq "\n") {
171 0 0         next if($self->{clients}->{$cid}->{buffer} eq ''); # Empty lines
172              
173             my %inmsg = (
174             message => $self->{clients}->{$cid}->{buffer},
175             releasetime => $now + $self->{clients}->{$cid}->{inmessagedelay},
176 0           );
177 0           push @{$self->{clients}->{$cid}->{inmessages}}, \%inmsg;
  0            
178 0           $self->{clients}->{$cid}->{buffer} = '';
179             } else {
180 0           $self->{clients}->{$cid}->{buffer} .= $buf;
181             }
182             }
183              
184 0 0 0       if($self->{interclackslock} && !$self->{clients}->{$cid}->{interclacksclient}) {
185             # We are locked into interclacks sync lock, but this is not the connection to master,
186             # so we don't handle the input buffer for this client at the moment.
187 0           next;
188             }
189              
190              
191 0           while(scalar @{$self->{clients}->{$cid}->{inmessages}}) {
  0            
192 0 0         last if($self->{clients}->{$cid}->{inmessages}->[0]->{releasetime} > $now);
193 0           my $inmsgtmp = shift @{$self->{clients}->{$cid}->{inmessages}};
  0            
194 0           my $inmsg = $inmsgtmp->{message};
195              
196             # Handle CLACKS identification header
197 0 0         if($inmsg =~ /^CLACKS\ (.+)/) {
198 0           $self->{clients}->{$cid}->{clientinfo} = $1;
199 0           $self->{clients}->{$cid}->{clientinfo} =~ s/\;/\_/g;
200 0           print "Client at ", $cid, " identified as ", $self->{clients}->{$cid}->{clientinfo}, "\n";
201 0           next;
202             }
203              
204 0           $self->{nodebug} = 0;
205 0           $self->{sendinterclacks} = 1;
206 0           $self->{discardafterlogging} = 0;
207             # Handle OVERHEAD messages before logging (for handling 'N' flag correctly)
208 0           $self->_handleMessageOverhead($cid, $inmsg);
209              
210             # Ignore other command when not authenticated
211 0 0         if(!$self->{clients}->{$cid}->{authok}) {
212 0           next;
213             }
214              
215 0 0         if(!$self->{nodebug}) {
216             # Add ALL incoming messages as debug-type messages to the outbox
217 0           my %tmp = (
218             sender => $cid,
219             type => 'DEBUG',
220             data => $inmsg,
221             );
222              
223 0           push @{$self->{outbox}}, \%tmp;
  0            
224             }
225              
226 0 0         if($self->{discardafterlogging}) {
227 0           next;
228             }
229              
230              
231              
232 0 0         if($inmsg =~ /^OVERHEAD\ /) { ## no critic (ControlStructures::ProhibitCascadingIfElse)
    0          
    0          
    0          
233             # Already handled
234 0           next;
235             } elsif($self->_handleMessageDirect($cid, $inmsg)) {
236             # Fallthrough
237             } elsif($self->_handleMessageCaching($cid, $inmsg)) {
238             # Fallthrough
239             } elsif($self->_handleMessageControl($cid, $inmsg)) {
240             # Fallthrough
241             # local managment commands
242             } else {
243 0           print STDERR "ERROR Unknown_command ", $inmsg, "\r\n";
244 0           $self->{sendinterclacks} = 0;
245 0           $self->{clients}->{$cid}->{outbuffer} .= "OVERHEAD E unknown_command " . $inmsg . "\r\n";
246             }
247              
248             # forward interclacks messages
249 0 0         if($self->{sendinterclacks}) {
250 0           foreach my $interclackscid (keys %{$self->{clients}}) {
  0            
251 0 0 0       if($cid eq $interclackscid || !$self->{clients}->{$interclackscid}->{interclacks}) {
252 0           next;
253             }
254 0           $self->{clients}->{$interclackscid}->{outbuffer} .= $inmsg . "\r\n";
255             }
256             }
257              
258             }
259              
260             }
261              
262             # Clean up algorithm, only run every so often
263 0 0         if($self->{nextcachecleanup} < $now) {
264 0           $self->_cacheCleanup();
265 0           $self->{nextcachecleanup} = $now + $self->{config}->{cachecleaninterval};
266              
267             }
268              
269 0           $self->_outboxToClientBuffer();
270 0           $self->_clientOutput();
271              
272 0 0         if($self->{workCount}) {
    0          
273 0           $self->{usleep} = 0;
274             } elsif($self->{usleep} < $self->{config}->{throttle}->{maxsleep}) {
275 0           $self->{usleep} += $self->{config}->{throttle}->{step};
276             }
277 0 0         if($self->{usleep}) {
278 0           sleep($self->{usleep} / 1000);
279             }
280             }
281              
282 0           print "Shutting down...\n";
283              
284             # Make sure we save the latest version of the persistance file
285 0           $self->_savePersistanceFile();
286              
287 0           sleep(0.5);
288 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
289 0           print "Removing client $cid\n";
290             # Try to notify the client (may or may not work);
291 0           $self->_evalsyswrite($self->{clients}->{$cid}->{socket}, "\r\nQUIT\r\n");
292              
293 0           delete $self->{clients}->{$cid};
294             }
295 0           print "All clients removed\n";
296              
297              
298 0           return;
299             }
300 0     0     sub _savePersistanceFile($self) {
  0            
  0            
301 0 0         if(!$self->{persistance}) {
302 0           return;
303             }
304              
305 0           print "Saving persistance file\n";
306              
307 0           my $tempfname = $self->{config}->{persistancefile} . '_';
308 0           my $backfname = $self->{config}->{persistancefile} . '_bck';
309 0 0         if($self->{savecache} == 1) {
310             # Normal savecache operation only
311 0           copy($self->{config}->{persistancefile}, $backfname);
312             }
313              
314 0           my $persistancedata = chr(0) . 'CLACKSV3' . Dump($self->{cache}) . chr(0) . 'CLACKSV3';
315 0           $self->_writeBinFile($tempfname, $persistancedata);
316 0           move($tempfname, $self->{config}->{persistancefile});
317              
318 0 0         if($self->{savecache} == 2) {
319             # Need to make sure we have a valid backup file, since we had a general problem while loading
320 0           copy($self->{config}->{persistancefile}, $backfname);
321             }
322              
323 0           return;
324             }
325              
326 0     0     sub _deref($self, $val) {
  0            
  0            
  0            
327 0 0         return if(!defined($val));
328              
329 0   0       while(ref($val) eq "SCALAR" || ref($val) eq "REF") {
330 0           $val = ${$val};
  0            
331 0 0         last if(!defined($val));
332             }
333              
334 0           return $val;
335             }
336              
337 0     0     sub _evalsyswrite($self, $socket, $buffer) {
  0            
  0            
  0            
  0            
338 0 0         return 0 unless(length($buffer));
339              
340 0           my $written = 0;
341 0           my $ok = 0;
342 0           eval { ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
343 0           $written = syswrite($socket, $buffer);
344 0           $ok = 1;
345             };
346 0 0 0       if($EVAL_ERROR || !$ok) {
347 0           print STDERR "Write error: $EVAL_ERROR\n";
348 0           return -1;
349             }
350              
351 0           return $written;
352             }
353              
354 0     0     sub _getTime($self) {
  0            
  0            
355 0           my $now = time + $self->{timeoffset};
356              
357 0           return $now;
358             }
359              
360 0     0     sub _slurpBinFile($self, $fname) {
  0            
  0            
  0            
361             # Read in file in binary mode, slurping it into a single scalar.
362             # We have to make sure we use binmode *and* turn on the line termination variable completly
363             # to work around the multiple idiosynchrasies of Perl on Windows
364 0 0         open(my $fh, "<", $fname) or croak($ERRNO);
365 0           local $INPUT_RECORD_SEPARATOR = undef;
366 0           binmode($fh);
367 0           my $data = <$fh>;
368 0           close($fh);
369              
370 0           return $data;
371             }
372              
373 0     0     sub _writeBinFile($self, $fname, $data) {
  0            
  0            
  0            
  0            
374             # Write file in binmode
375             # We have to make sure we use binmode *and* turn on the line termination variable completly
376             # to work around the multiple idiosynchrasies of Perl on Windows
377 0 0         open(my $fh, ">", $fname) or croak($ERRNO);
378 0           local $INPUT_RECORD_SEPARATOR = undef;
379 0           binmode($fh);
380 0           print $fh $data;
381 0           close($fh);
382              
383 0           return 1;
384             }
385              
386 0     0     sub _restorePersistanceFile($self) {
  0            
  0            
387 0           my $previousfname = $self->{config}->{persistancefile} . '_bck';
388 0           my $tempfname = $self->{config}->{persistancefile} . '_';
389 0           my $loadok = 0;
390 0 0         if(-f $self->{config}->{persistancefile}) {
391 0           print "Trying to load persistance file ", $self->{config}->{persistancefile}, "\n";
392 0           $loadok = $self->_loadPersistanceFile($self->{config}->{persistancefile});
393             }
394              
395 0 0 0       if(!$loadok && -f $previousfname) {
396 0           print "Trying to load backup (previous) persistance file ", $previousfname, "\n";
397 0           $loadok = $self->_loadPersistanceFile($previousfname);
398 0 0         if($loadok) {
399 0           $self->{savecache} = 2; # Force saving a new persistance file plus a new backup
400             }
401             }
402 0 0 0       if(!$loadok && -f $tempfname) {
403 0           print "Oh no. As a final, desperate solution, trying to load a 'temporary file while saving' persistance file ", $tempfname, "\n";
404 0           $loadok = $self->_loadPersistanceFile($tempfname);
405 0 0         if($loadok) {
406 0           $self->{savecache} = 2; # Force saving a new persistance file plus a new backup
407             }
408             }
409              
410 0 0         if(!$loadok) {
411 0           print "Sorry, no valid persistance file found. Starting server 'blankety-blank'\n";
412 0           $self->{savecache} = 2;
413             } else {
414 0           print "Persistance file loaded\n";
415             }
416            
417 0           return;
418             }
419              
420 0     0     sub _init($self) {
  0            
  0            
421 0 0 0       if(defined($self->{initHasRun}) && $self->{initHasRun}) {
422             # NOT ALLOWED!
423 0           croak("Multiple calls to _init() are not allowed!");
424             }
425              
426 0           my @paths;
427 0 0         if(defined($ENV{'PC_CONFIG_PATHS'})) {
428 0           push @paths, split/\:/, $ENV{'PC_CONFIG_PATHS'};
429 0           print "Found config paths:\n", Dumper(\@paths), " \n";
430             } else {
431 0           print("PC_CONFIG_PATHS undefined, falling back to legacy mode\n");
432 0           @paths = ('', 'configs/');
433             }
434              
435 0           my $filedata;
436 0           my $fname = $self->{configfile};
437 0           foreach my $path (@paths) {
438 0 0 0       if($path ne '' && $path !~ /\/$/) {
439 0           $path .= '/';
440             }
441 0           my $fullfname = $path . $fname;
442 0 0         next unless (-f $fullfname);
443 0           print " Loading config file $fullfname\n";
444              
445 0           $filedata = $self->_slurpBinFile($fullfname);
446              
447 0           foreach my $varname (keys %ENV) {
448 0 0         next unless $varname =~ /^PC\_/;
449              
450 0           my $newval = $ENV{$varname};
451 0           $filedata =~ s/$varname/$newval/g;
452             }
453              
454 0           last;
455             }
456              
457 0 0 0       if(!defined($filedata) || $filedata eq "") {
458 0           croak("Can't load config file: Not found or empty!");
459             }
460              
461 0           print "------- Parsing config file $fname ------\n";
462 0           my $config = XMLin($filedata, ForceArray => [ 'ip', 'socket' ]);
463              
464 0           my $hname = hostname;
465              
466             # Copy hostname-specific stuff to root if it exists
467 0 0         if(defined($config->{hosts}->{$hname})) {
468 0           foreach my $key (keys %{$config->{hosts}->{$hname}}) {
  0            
469 0           $config->{$key} = $config->{hosts}->{$hname}->{$key};
470             }
471             }
472              
473 0           $self->{config} = $config;
474              
475 0 0         if(!defined($self->{config}->{throttle}->{maxsleep})) {
476 0           $self->{config}->{throttle}->{maxsleep} = 100;
477             }
478 0 0         if(!defined($self->{config}->{throttle}->{step})) {
479 0           $self->{config}->{throttle}->{step} = 10;
480             }
481              
482 0           $self->{usleep} = 0;
483              
484 0 0 0       if(!defined($self->{config}->{ssl}) ||
      0        
485             !defined($self->{config}->{ssl}->{cert}) ||
486             !defined($self->{config}->{ssl}->{key})) {
487 0           croak("Missing or incomplete SSL config!");
488             }
489 0 0         if(!-f $self->{config}->{ssl}->{cert}) {
490 0           croak("SSL cert file " . $self->{config}->{ssl}->{cert} . " not found!");
491             }
492 0 0         if(!-f $self->{config}->{ssl}->{key}) {
493 0           croak("SSL key file " . $self->{config}->{ssl}->{key} . " not found!");
494             }
495              
496 0 0         if(!defined($self->{config}->{username})) {
497 0           croak("Username not defined!");
498             }
499 0 0         if(!defined($self->{config}->{password})) {
500 0           croak("Password not defined!");
501             }
502 0           $self->{authtoken} = encode_base64($self->{config}->{username}, '') . ':' . encode_base64($self->{config}->{password}, '');
503              
504 0 0         if(defined($self->{config}->{persistancefile})) {
505 0           $self->{persistance} = 1;
506             } else {
507 0           $self->{persistance} = 0;
508             }
509              
510 0 0         if(!defined($self->{config}->{persistanceinterval})) {
511 0           $self->{persistanceinterval} = 10;
512             } else {
513 0           $self->{persistanceinterval} = $self->{config}->{persistanceinterval};
514             }
515              
516 0 0         if(!defined($self->{config}->{interclacksreconnecttimeout})) {
517 0           $self->{config}->{interclacksreconnecttimeout} = 30;
518             }
519              
520 0 0         if(!defined($self->{config}->{authtimeout})) {
521 0           $self->{config}->{authtimeout} = 15;
522             }
523              
524 0 0         if(!defined($self->{config}->{deletedcachetime})) {
525 0           $self->{config}->{deletedcachetime} = 60 * 60; # 1 hour
526             }
527 0 0         if(!defined($self->{config}->{stalecachetime})) {
528 0           $self->{config}->{stalecachetime} = 60 * 60 * 24; # 1 day
529             }
530              
531 0 0         if(!defined($self->{config}->{cachecleaninterval})) {
532 0           $self->{config}->{cachecleaninterval} = 60; # 1 minute
533             }
534              
535             # Init run() variables
536 0           $self->{savecache} = 0;
537 0           $self->{lastsavecache} = 0;
538 0           $self->{outbox} = [];
539 0           $self->{toremove} = [];
540 0           $self->{clients} = {};
541 0           $self->{shutdowntime} = 0;
542 0           $self->{selector} = IO::Select->new();
543 0           $self->{interclackslock} = 0;
544 0           $self->{nextinterclackscheck} = 0;
545 0           $self->{keepRunning} = 1;
546 0           $self->{nextcachecleanup} = 0;
547 0           $self->{initHasRun} = 1;
548              
549 0           my @tcpsockets;
550              
551 0 0         if(defined($config->{ip})) {
552 0 0         if(!defined($config->{port})) {
553 0           croak("At least one IP defined, but no TCP port!");
554             }
555 0           foreach my $ip (@{$config->{ip}}) {
  0            
556             my $tcp = IO::Socket::IP->new(
557             LocalHost => $ip,
558             LocalPort => $config->{port},
559 0 0         Listen => 1,
560             Blocking => 0,
561             ReuseAddr => 1,
562             Proto => 'tcp',
563             ) or croak($ERRNO);
564             #binmode($tcp, ':bytes');
565 0           push @tcpsockets, $tcp;
566 0           print "Listening on $ip:", $config->{port}, "/tcp\n";
567             }
568             }
569              
570 0 0 0       if(defined($config->{socket}) || defined($self->{config}->{master}->{socket})) {
571 0           my $udsloaded = 0;
572 0           eval { ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
573 0           require IO::Socket::UNIX;
574 0           $udsloaded = 1;
575             };
576 0 0         if(!$udsloaded) {
577 0           croak("Specified a unix domain socket, but i couldn't load IO::Socket::UNIX!");
578             }
579              
580             # Add the ClientID stuff to Unix domain sockets as well. We don't do this in the BEGIN{} block
581             # since we are not yet sure we are going to load IO::Socket::UNIX in the first place
582             {
583 1     1   21 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  1         2  
  1         9989  
  0            
584 0           *{"IO::Socket::UNIX::_setClientID"} = sub {
585 0     0     my ($self, $cid) = @_;
586            
587 0           ${*$self}{'__client_id'} = $cid; ## no critic (References::ProhibitDoubleSigils)
  0            
588 0           return;
589 0           };
590            
591 0           *{"IO::Socket::UNIX::_getClientID"} = sub {
592 0     0     my ($self) = @_;
593            
594 0   0       return ${*$self}{'__client_id'} || ''; ## no critic (References::ProhibitDoubleSigils)
595 0           };
596             }
597             }
598              
599 0 0         if(defined($config->{socket})) {
600 0           foreach my $socket (@{$config->{socket}}) {
  0            
601 0 0         if(-S $socket) {
602 0           print "Removing old unix domain socket file $socket\n";
603 0           unlink $socket;
604             }
605 0 0         my $tcp = IO::Socket::UNIX->new(
606             Type => SOCK_STREAM(),
607             Local => $socket,
608             Listen => 1,
609             #Blocking => 0,
610             ) or croak($ERRNO);
611 0           $tcp->blocking(0);
612             #binmode($tcp, ':bytes');
613 0           push @tcpsockets, $tcp;
614 0           print "Listening on Unix domain socket $socket\n";
615              
616 0 0 0       if(defined($config->{socketchmod}) && $config->{socketchmod} ne '') {
617 0           my $cmd = 'chmod ' . $config->{socketchmod} . ' ' . $socket;
618 0           print $cmd, "\n";
619 0           `$cmd`;
620             }
621             }
622             }
623              
624 0           $self->{tcpsockets} = \@tcpsockets;
625              
626              
627 0           print "Ready.\n";
628              
629              
630 0           return;
631             }
632              
633 0     0     sub _loadPersistanceFile($self, $fname) {
  0            
  0            
  0            
634 0           my $alreadyupgraded = 0;
635              
636             retry:
637 0 0         if(!-f $fname) {
638             # Does not exist
639 0           carp("$fname not found");
640 0           return 0;
641             }
642              
643 0           my $data = $self->_slurpBinFile($fname);
644 0 0         if(length($data) < 11) {
645 0           carp("$fname too small");
646             # Invalid file
647 0           return 0;
648             }
649 0 0         if(substr($data, 0, 9) ne chr(0) . 'CLACKSV3') {
650 0 0         if($alreadyupgraded) {
651 0           carp("Upgrade resulted in invalog file (wrong prefix)");
652             # Something went wrong with inplace upgrade
653 0           return;
654             }
655 0           $alreadyupgraded = 1;
656 0 0         if(!$self->_inplaceUpgrade($fname)) {
657 0           carp("Inplace upgrade failed");
658             # Could not upgrade
659 0           return 0;
660             }
661 0           goto retry;
662             }
663              
664 0 0         if(length($data) < 18) {
665 0           carp("Incomplete V3 persistance file " . $fname . " (too small)!");
666 0           return 0;
667             }
668              
669 0           substr($data, 0, 9, ''); # Remove header
670              
671 0 0         if(substr($data, -9, 9) ne chr(0) . 'CLACKSV3') {
672             # Missing end bytes
673 0           carp("Incomplete V3 persistance file " . $fname . " (missing end bytes)!");
674 0           return 0; # Fail
675             }
676 0           substr($data, -9, 9, ''); # Remove end bytes
677              
678 0           $self->{cache} = {};
679 0 0         if(length($data)) {
680 0           my $loadok = 0;
681 0           eval {
682 0           $self->{cache} = Load($data);
683 0           $loadok = 1;
684             };
685 0 0         if(!$loadok) {
686 0           carp("Invalid V3 persistance file " . $fname . "!");
687 0           return 0; # Fail
688             }
689             }
690              
691 0           return 1;
692             }
693              
694 0     0     sub _inplaceUpgrade($self, $fname) {
  0            
  0            
  0            
695 0           my %clackscache;
696             my %clackscachetime;
697 0           my %clackscacheaccesstime;
698              
699 0           print "Inplace-upgrading persistance file $fname...\n";
700              
701 0           my $now = $self->_getTime();
702              
703             # Use old loader algorithm to load file
704 0 0         if(open(my $ifh, '<', $fname)) {
705 0           my $line = <$ifh>;
706 0           my $timestampline = <$ifh>;
707 0           my $accesstimeline = <$ifh>;
708 0           my $endline = <$ifh>;
709 0           my $needupgrade = 0;
710 0           close $ifh;
711              
712 0           chomp $line;
713 0           chomp $timestampline;
714 0           chomp $accesstimeline;
715              
716 0 0 0       if(!defined($endline) && $accesstimeline eq 'ENDBYTES') {
717 0           $endline = 'ENDBYTES';
718 0           $accesstimeline = '';
719 0           $needupgrade = 1;
720             } else {
721 0           chomp $endline;
722             }
723              
724 0 0 0       if(!defined($line) || !defined($timestampline) || $endline ne 'ENDBYTES') {
      0        
725 0           carp("Invalid persistance file " . $fname . "! File is incomplete!");
726 0           return 0; # Fail
727             }
728              
729 0           my $loadok = 0;
730              
731 0 0         if($line ne '') {
732 0           eval {
733 0           $line = decode_base64($line);
734 0           $line = Load($line);
735 0           $loadok = 1;
736             };
737 0 0         if(!$loadok) {
738 0           carp("Invalid persistance file " . $fname . "! Failed to decode data line!");
739 0           return 0; # Fail
740             }
741             }
742 0           %clackscache = %{$line};
  0            
743              
744             # Mark all data as current as a fallback
745 0           foreach my $key (keys %clackscache) {
746 0           $clackscachetime{$key} = $now;
747             }
748              
749 0 0         if($timestampline ne '') {
750 0           $loadok = 0;
751 0           eval {
752 0           $timestampline = decode_base64($timestampline);
753 0           $timestampline = Load($timestampline);
754 0           $loadok = 1;
755             };
756 0 0         if(!$loadok) {
757 0           carp("Invalid persistance file " . $fname . "! Failed to decode timestamp line, using current time!");
758 0           return 0; # Fail
759             } else {
760 0           my %clackstemp = %{$timestampline};
  0            
761 0           foreach my $key (keys %clackstemp) {
762 0           $clackscachetime{$key} = $clackstemp{$key};
763             }
764             }
765             }
766              
767 0 0         if($needupgrade) {
    0          
768 0           print "Pre-Version 22 persistance file detected. Upgrading automatically.\n";
769 0           foreach my $key (keys %clackscache) {
770 0           $clackscacheaccesstime{$key} = $now;
771             }
772             } elsif($accesstimeline ne '') {
773 0           $loadok = 0;
774 0           eval {
775 0           $accesstimeline = decode_base64($accesstimeline);
776 0           $accesstimeline = Load($accesstimeline);
777 0           $loadok = 1;
778             };
779 0 0         if(!$loadok) {
780 0           carp("Invalid persistance file " . $fname . "! Failed to decode timestamp line, using current time!");
781 0           return 0; # Fail
782             } else {
783 0           %clackscacheaccesstime = %{$accesstimeline};
  0            
784             }
785             }
786             } else {
787             # Fail
788 0           return 0;
789             }
790              
791             # Turn into new format
792 0           my %cache;
793             # not-deleted sets
794 0           foreach my $key (keys %clackscache) {
795 0           my $cachetime = $now;
796 0           my $accesstime = $now;
797 0 0         if(defined($clackscachetime{$key})) {
798 0           $cachetime = $clackscachetime{$key};
799             }
800 0 0         if(defined($clackscacheaccesstime{$key})) {
801 0           $accesstime = $clackscacheaccesstime{$key};
802             }
803             $cache{$key} = {
804 0           data => $clackscache{$key},
805             cachetime => $cachetime,
806             accesstime => $accesstime,
807             deleted => 0,
808             };
809             }
810              
811             # deleted sets
812 0           foreach my $key (keys %clackscachetime) {
813 0 0         if(defined($cache{$key})) {
814             # not deleted
815 0           next;
816             }
817              
818 0           my $cachetime = $clackscachetime{$key};
819 0           my $accesstime = $now;
820 0 0         if(defined($clackscacheaccesstime{$key})) {
821 0           $accesstime = $clackscacheaccesstime{$key};
822             }
823 0           $cache{$key} = {
824             data => '',
825             cachetime => $cachetime,
826             accesstime => $accesstime,
827             deleted => 0,
828             };
829             }
830              
831 0           my $converted = chr(0) . 'CLACKSV3' . Dump(\%cache) . chr(0) . 'CLACKSV3';
832 0           $self->_writeBinFile($fname, $converted);
833              
834 0           print "...upgrade complete.\n";
835              
836 0           return 1;
837             }
838              
839 0     0     sub _addInterclacksLink($self) {
  0            
  0            
840 0           my $now = $self->_getTime();
841              
842 0           my $mcid;
843 0 0         if(defined($self->{config}->{master}->{socket})) {
844 0           $mcid = 'unixdomainsocket:interclacksmaster';
845             } else {
846 0           $mcid = $self->{config}->{master}->{ip}->[0] . ':' . $self->{config}->{master}->{port};
847             }
848 0 0 0       if(!defined($self->{clients}->{$mcid}) && $self->{nextinterclackscheck} < $now) {
849 0           $self->{nextinterclackscheck} = $now + $self->{config}->{interclacksreconnecttimeout} + int(rand(10));
850              
851 0           print "Connect to master\n";
852 0           my $msocket;
853              
854 0 0         if(defined($self->{config}->{master}->{socket})) {
855             $msocket = IO::Socket::UNIX->new(
856 0           Peer => $self->{config}->{master}->{socket}->[0],
857             Type => SOCK_STREAM,
858             );
859             } else {
860             $msocket = IO::Socket::IP->new(
861             PeerHost => $self->{config}->{master}->{ip}->[0],
862             PeerPort => $self->{config}->{master}->{port},
863 0           Type => SOCK_STREAM,
864             Timeout => 5,
865             );
866             }
867 0 0         if(!defined($msocket)) {
868 0           print STDERR "Can't connect to MASTER via interclacks!\n";
869             } else {
870 0           print "connected to master\n";
871              
872 0 0         if(ref $msocket ne 'IO::Socket::UNIX') {
873             # ONLY USE SSL WHEN RUNNING OVER THE NETWORK
874             # There is simply no point in running it over a local socket.
875 0           my $encrypted = IO::Socket::SSL->start_SSL($msocket,
876             SSL_verify_mode => SSL_VERIFY_NONE,
877             );
878 0 0         if(!$encrypted) {
879 0           print "startSSL failed: ", $SSL_ERROR, "\n";
880 0           next;
881             }
882             }
883              
884 0           $msocket->blocking(0);
885             #binmode($msocket, ':bytes');
886             my %tmp = (
887             buffer => '',
888             charbuffer => [],
889             listening => {},
890             socket => $msocket,
891             lastping => $now,
892             mirror => 0,
893             outbuffer => "CLACKS PageCamel $VERSION in interclacks client mode\r\n" . # Tell the server we are using PageCamel Interclacks...
894             "OVERHEAD A " . $self->{authtoken} . "\r\n" . # ...send Auth token
895             "OVERHEAD I 1\r\n", # ...and turn interclacks master mode ON on remote side
896             clientinfo => 'Interclacks link',
897             client_timeoffset => 0,
898             interclacks => 1,
899             interclacksclient => 1,
900             lastinterclacksping => $now,
901             lastmessage => $now,
902             authtimeout => $now + $self->{config}->{authtimeout},
903 0           authok => 0,
904             failcount => 0,
905             outmessages => [],
906             inmessages => [],
907             messagedelay => 0,
908             inmessagedelay => 0,
909             outmessagedelay => 0,
910             );
911              
912 0 0         if(defined($self->{config}->{master}->{ip})) {
913 0           $tmp{host} = $self->{config}->{master}->{ip}->[0];
914 0           $tmp{port} = $self->{config}->{master}->{port};
915             }
916 0           $self->{clients}->{$mcid} = \%tmp;
917 0           $msocket->_setClientID($mcid);
918 0           $self->{selector}->add($msocket);
919              
920 0           $self->{workCount}++;
921             }
922             }
923 0           return;
924             }
925              
926 0     0     sub _addNewClients($self) {
  0            
  0            
927 0           my $now = $self->_getTime();
928 0           foreach my $tcpsocket (@{$self->{tcpsockets}}) {
  0            
929 0           my $clientsocket = $tcpsocket->accept;
930 0 0         if(defined($clientsocket)) {
931 0           $clientsocket->blocking(0);
932 0           my ($cid, $chost, $cport);
933 0 0         if(ref $tcpsocket eq 'IO::Socket::UNIX') {
934 0           $chost = 'unixdomainsocket';
935 0           $cport = $now . ':' . int(rand(1_000_000));
936             } else {
937 0           ($chost, $cport) = ($clientsocket->peerhost, $clientsocket->peerport);
938             }
939 0           print "Got a new client $chost:$cport!\n";
940 0           $cid = "$chost:$cport";
941 0           foreach my $debugcid (keys %{$self->{clients}}) {
  0            
942 0 0         if($self->{clients}->{$debugcid}->{mirror}) {
943 0           $self->{clients}->{$debugcid}->{outbuffer} .= "DEBUG CONNECTED=" . $cid . "\r\n";
944             }
945             }
946              
947 0 0         if(ref $clientsocket ne 'IO::Socket::UNIX') {
948             # ONLY USE SSL WHEN RUNNING OVER THE NETWORK
949             # There is simply no point in running it over a local socket.
950             my $encrypted = IO::Socket::SSL->start_SSL($clientsocket,
951             SSL_server => 1,
952             SSL_cert_file => $self->{config}->{ssl}->{cert},
953             SSL_key_file => $self->{config}->{ssl}->{key},
954             SSL_cipher_list => 'ALL:!ADH:!RC4:+HIGH:+MEDIUM:!LOW:!SSLv2:!SSLv3!EXPORT',
955             SSL_create_ctx_callback => sub {
956 0     0     my $ctx = shift;
957              
958             # Enable workarounds for broken clients
959 0           Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL); ## no critic (Subroutines::ProhibitAmpersandSigils)
960              
961             # Disable session resumption completely
962 0           Net::SSLeay::CTX_set_session_cache_mode($ctx, $SSL_SESS_CACHE_OFF);
963              
964             # Disable session tickets
965 0           Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_NO_TICKET); ## no critic (Subroutines::ProhibitAmpersandSigils)
966             },
967 0           );
968 0 0         if(!$encrypted) {
969 0           print "startSSL failed: ", $SSL_ERROR, "\n";
970 0           next;
971             }
972             }
973              
974 0           $clientsocket->blocking(0);
975             #binmode($clientsocket, ':bytes');
976             #$clientsocket->{clacks_cid} = $cid;
977             my %tmp = (
978             buffer => '',
979             charbuffer => [],
980             listening => {},
981             socket => $clientsocket,
982             lastping => $now,
983             mirror => 0,
984             outbuffer => "CLACKS PageCamel $VERSION\r\n" .
985             "OVERHEAD M Authentication required\r\n", # Informal message
986             clientinfo => 'UNKNOWN',
987             client_timeoffset => 0,
988             host => $chost,
989             port => $cport,
990             interclacks => 0,
991             interclacksclient => 0,
992             lastinterclacksping => 0,
993             lastmessage => $now,
994             authtimeout => $now + $self->{config}->{authtimeout},
995 0           authok => 0,
996             failcount => 0,
997             outmessages => [],
998             inmessages => [],
999             inmessagedelay => 0,
1000             outmessagedelay => 0,
1001             );
1002 0           if(0 && $self->{isDebugging}) {
1003             $tmp{authok} = 1;
1004             $tmp{outbuffer} .= "OVERHEAD M debugmode_auth_not_really_required\r\n"
1005             }
1006 0           $self->{clients}->{$cid} = \%tmp;
1007 0           $clientsocket->_setClientID($cid);
1008 0           $self->{selector}->add($clientsocket);
1009 0           $self->{workCount}++;
1010             }
1011             }
1012            
1013 0           return;
1014             }
1015              
1016 0     0     sub _disconnectClients($self) {
  0            
  0            
1017 0           my $now = $self->_getTime();
1018              
1019             # Check if there are any clients to disconnect...
1020 0           my $pingtime = $now - $self->{config}->{pingtimeout};
1021 0           my $interclackspingtime = $now - $self->{config}->{interclackspingtimeout};
1022 0           my $interclackspinginterval = $now - int($self->{config}->{interclackspingtimeout} / 3);
1023 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
1024 0 0         if(!$self->{clients}->{$cid}->{socket}->connected) {
1025 0           push @{$self->{toremove}}, $cid;
  0            
1026 0           next;
1027             }
1028 0 0         if(!$self->{clients}->{$cid}->{interclacks}) {
1029 0 0 0       if($self->{clients}->{$cid}->{lastping} > 0 && $self->{clients}->{$cid}->{lastping} < $pingtime) {
1030 0           $self->_evalsyswrite($self->{clients}->{$cid}->{socket}, "\r\nTIMEOUT\r\n");
1031 0           push @{$self->{toremove}}, $cid;
  0            
1032 0           next;
1033             }
1034             } else {
1035 0 0         if($self->{clients}->{$cid}->{lastping} < $interclackspingtime) {
1036 0           $self->_evalsyswrite($self->{clients}->{$cid}->{socket}, "\r\nTIMEOUT\r\n");
1037 0           push @{$self->{toremove}}, $cid;
  0            
1038 0           next;
1039             }
1040             }
1041              
1042 0 0 0       if($self->{clients}->{$cid}->{interclacks} && $self->{clients}->{$cid}->{lastinterclacksping} < $interclackspinginterval) {
1043 0           $self->{clients}->{$cid}->{lastinterclacksping} = $now;
1044 0           $self->{clients}->{$cid}->{outbuffer} .= "PING\r\n";
1045             }
1046              
1047 0 0 0       if(!$self->{clients}->{$cid}->{authok} && $self->{clients}->{$cid}->{authtimeout} < $now) {
1048             # Authentication timeout!
1049 0           push @{$self->{toremove}}, $cid;
  0            
1050             }
1051             }
1052              
1053             # ...and disconnect them
1054 0 0         if(scalar @{$self->{toremove}}) {
  0            
1055             # Make sure we handle any last messages, or at least try to. This should allow us to at least try to adhere to the
1056             # protocol in some cases (auth failure, etc)
1057 0           $self->_outboxToClientBuffer();
1058 0           for(1..5) {
1059 0           my @flushed;
1060 0           foreach my $cid (@{$self->{toremove}}) {
  0            
1061 0 0         next if(contains($cid, \@flushed));
1062 0           push @flushed, $cid;
1063 0 0         next if(!length($self->{clients}->{$cid}->{outbuffer}));
1064 0           print "Flushing $cid for removal...\n";
1065 0           $self->_clientOutput($cid);
1066             }
1067 0           sleep(0.02);
1068             }
1069             }
1070              
1071 0           while((my $cid = shift @{$self->{toremove}})) {
  0            
1072             # In some circumstances, there may be multiple @{$self->{toremove}} entries for the same client. Ignore them...
1073 0 0         if(defined($self->{clients}->{$cid})) {
1074 0           print "Removing client $cid\n";
1075 0           foreach my $debugcid (keys %{$self->{clients}}) {
  0            
1076 0 0         if($self->{clients}->{$debugcid}->{mirror}) {
1077 0           $self->{clients}->{$debugcid}->{outbuffer} .= "DEBUG DISCONNECTED=" . $cid . " (" . length($self->{clients}->{$cid}->{outbuffer}) . " bytes discarded from outbuffer)\r\n";
1078 0 0 0       if($self->{clients}->{$cid}->{interclacksclient} && $self->{interclackslock}) {
1079 0           $self->{clients}->{$debugcid}->{outbuffer} .= "DEBUG DISCONNECTED=" . $cid . " (Unlocking interclacks mid-sync)\r\n";
1080             }
1081             }
1082             }
1083              
1084 0 0 0       if($self->{clients}->{$cid}->{interclacksclient} && $self->{interclackslock}) {
1085 0           print "...this one is interclacks master and has us locked - UNLOCKING mid-sync!\n";
1086 0           $self->{interclackslock} = 0;
1087             }
1088              
1089 0           $self->{selector}->remove($self->{clients}->{$cid}->{socket});
1090 0           delete $self->{clients}->{$cid};
1091             }
1092              
1093 0           $self->{workCount}++;
1094             }
1095              
1096 0           return;
1097             }
1098              
1099 0     0     sub _clientInput($self) {
  0            
  0            
1100 0           my $now = $self->_getTime();
1101              
1102             # **** READ FROM CLIENTS ****
1103 0           my $hasoutbufferwork = 0;
1104 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
1105 0 0         if(length($self->{clients}->{$cid}->{buffer}) > 0) {
1106             # Found some work to do
1107 0           $hasoutbufferwork = 1;
1108 0           last;
1109             }
1110             }
1111 0           my $selecttimeout = 0.5; # Half a second
1112 0 0         if($hasoutbufferwork) {
1113 0           $selecttimeout = 0.05;
1114             }
1115              
1116 0           my @inclients = $self->{selector}->can_read($selecttimeout);
1117 0           foreach my $clientsocket (@inclients) {
1118 0           my $cid = $clientsocket->_getClientID();
1119              
1120 0           my $totalread = 0;
1121 0           my $readchunksleft = 3;
1122 0           while(1) {
1123 0           my $rawbuffer;
1124 0           my $readok = 0;
1125 0           eval {
1126 0           sysread($self->{clients}->{$cid}->{socket}, $rawbuffer, 1_000_000); # Read at most 1 Meg at a time
1127 0           $readok = 1;
1128             };
1129 0 0         if(!$readok) {
1130 0           push @{$self->{toremove}}, $cid;
  0            
1131 0           last;
1132             }
1133 0 0 0       if(defined($rawbuffer) && length($rawbuffer)) {
1134 0           $totalread += length($rawbuffer);
1135 0           push @{$self->{clients}->{$cid}->{charbuffer}}, split//, $rawbuffer;
  0            
1136 0           $readchunksleft--;
1137 0 0         if(!$readchunksleft) {
1138 0           last;
1139             }
1140 0           next;
1141             }
1142 0           last;
1143             }
1144            
1145             # Check if we could read data from a socket that was marked as readable.
1146             # Thanks to SSL, this might ocxasionally fail. Don't bail out at the first
1147             # error, only if multiple happen one after the other
1148 0 0         if($totalread) {
1149 0           $self->{clients}->{$cid}->{failcount} = 0;
1150             } else {
1151 0           $self->{clients}->{$cid}->{failcount}++;
1152            
1153 0 0         if($self->{clients}->{$cid}->{failcount} > 5) {
1154             # Socket was active multiple times but delivered no data?
1155             # EOF, maybe, possible, perhaps?
1156 0           push @{$self->{toremove}}, $cid;
  0            
1157             }
1158             }
1159             }
1160              
1161 0           return;
1162             }
1163              
1164             # $forceclientid let's us "force" working on a specific client only
1165 0     0     sub _clientOutput($self, $forceclientid = '') {
  0            
  0            
  0            
1166 0           my $now = $self->_getTime();
1167            
1168             # **** WRITE TO CLIENTS ****
1169 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
1170 0 0 0       if($forceclientid ne '' && $forceclientid ne $cid) {
1171 0           next;
1172             }
1173 0 0 0       if($cid ne $forceclientid && contains($cid, $self->{toremove})) {
1174 0           next;
1175             }
1176 0 0         if(length($self->{clients}->{$cid}->{outbuffer})) {
    0          
1177 0           $self->{clients}->{$cid}->{lastmessage} = $now;
1178             } elsif(($self->{clients}->{$cid}->{lastmessage} + 60) < $now) {
1179 0           $self->{clients}->{$cid}->{lastmessage} = $now;
1180 0           $self->{clients}->{$cid}->{outbuffer} .= "NOP\r\n"; # send "No OPerations" command, just to
1181             # check if socket is still open
1182             }
1183              
1184 0 0         next if(!length($self->{clients}->{$cid}->{outbuffer}));
1185              
1186             # Output bandwidth-limited stuff, in as big chunks as possible
1187 0           my $written;
1188 0           $self->{workCount}++;
1189 0           eval { ## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
1190 0           $written = syswrite($self->{clients}->{$cid}->{socket}, $self->{clients}->{$cid}->{outbuffer});
1191             };
1192 0 0         if($EVAL_ERROR) {
1193 0           print STDERR "Write error: $EVAL_ERROR\n";
1194 0           push @{$self->{toremove}}, $cid;
  0            
1195 0           next;
1196             }
1197 0 0 0       if(!$self->{clients}->{$cid}->{socket}->opened || $self->{clients}->{$cid}->{socket}->error || ($ERRNO ne '' && !$ERRNO{EWOULDBLOCK})) {
      0        
      0        
1198 0           print STDERR "webPrint write failure: $ERRNO\n";
1199 0           push @{$self->{toremove}}, $cid;
  0            
1200 0           next;
1201             }
1202              
1203 0 0 0       if(defined($written) && $written) {
1204 0 0         if(length($self->{clients}->{$cid}->{outbuffer}) == $written) {
1205 0           $self->{clients}->{$cid}->{outbuffer} = '';
1206             } else {
1207 0           $self->{clients}->{$cid}->{outbuffer} = substr($self->{clients}->{$cid}->{outbuffer}, $written);
1208             }
1209             }
1210             }
1211              
1212 0           return;
1213             }
1214              
1215 0     0     sub _cacheCleanup($self) {
  0            
  0            
1216 0           my $now = $self->_getTime();
1217              
1218 0           my $deletedtime = $now - $self->{config}->{deletedcachetime};
1219 0           my $staletime = $now - $self->{config}->{stalecachetime};
1220 0           foreach my $ckey (keys %{$self->{cache}}) {
  0            
1221             # Clean deleted entries. Ignore any non-"DELETED" entries as these are run from a different timer
1222 0 0 0       if($self->{cache}->{$ckey}->{deleted} && $self->{cache}->{$ckey}->{cachetime} < $deletedtime) {
1223 0           delete $self->{cache}->{$ckey};
1224              
1225 0           my %tmp = (
1226             sender => 'SERVERCACHE',
1227             type => 'DEBUG',
1228             data => 'CLEANDELETED=' . $ckey,
1229             );
1230              
1231 0           push @{$self->{outbox}}, \%tmp;
  0            
1232 0           $self->{savecache} = 1;
1233 0           next;
1234             }
1235              
1236             # Forget entries that have not been accesses in a long time (stale entries).
1237             # Ignore any "DELETED" entries, as these are run from a different timer
1238 0 0 0       if(!$self->{cache}->{$ckey}->{deleted} && $self->{cache}->{$ckey}->{accesstime} < $staletime) {
1239 0           delete $self->{cache}->{$ckey};
1240              
1241 0           my %tmp = (
1242             sender => 'SERVERCACHE',
1243             type => 'DEBUG',
1244             data => 'CLEANSTALE=' . $ckey,
1245             );
1246              
1247 0           push @{$self->{outbox}}, \%tmp;
  0            
1248 0           $self->{savecache} = 1;
1249 0           next;
1250             }
1251             }
1252              
1253 0           return;
1254             }
1255              
1256 0     0     sub _outboxToClientBuffer($self) {
  0            
  0            
1257 0           my $now = $self->_getTime();
1258              
1259             # Outbox contains the messages that have to be forwarded to the clients when listening (or when the connection is in interclacks mode)
1260             # We iterate over the outbox and put those messages into the output buffers of the corresponding client connection
1261 0           while((my $line = shift @{$self->{outbox}})) {
  0            
1262 0           $self->{workCount}++;
1263 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
1264 0 0 0       if($line->{type} eq 'DEBUG' && $self->{clients}->{$cid}->{mirror}) {
1265 0           $self->{clients}->{$cid}->{outbuffer} .= "DEBUG " . $line->{sender} . "=". $line->{data} . "\r\n";
1266             }
1267              
1268 0 0         if($cid eq $line->{sender}) {
1269 0           next;
1270             }
1271              
1272 0 0 0       if($line->{type} ne 'DEBUG' && defined($self->{clients}->{$cid}->{listening}->{$line->{name}})) {
1273             # Just buffer in the clients outbuffers
1274 0 0         if($line->{type} eq 'NOTIFY') {
    0          
    0          
1275 0           $self->{clients}->{$cid}->{outbuffer} .= "NOTIFY ". $line->{name} . "\r\n";
1276             } elsif($line->{type} eq 'SET') {
1277 0           $self->{clients}->{$cid}->{outbuffer} .= "SET ". $line->{name} . "=" . $line->{value} . "\r\n";
1278             } elsif($line->{type} eq 'SETANDSTORE') {
1279             # We forward SETANDSTORE as such only over interclacks connections. Basic clients don't have a cache,
1280             # so we only send a SET command
1281 0 0         if($self->{clients}->{$cid}->{interclacks}) {
1282 0           $self->{clients}->{$cid}->{outbuffer} .= "SETANDSTORE ". $line->{name} . "=" . $line->{value} . "\r\n";
1283             } else {
1284 0           $self->{clients}->{$cid}->{outbuffer} .= "SET ". $line->{name} . "=" . $line->{value} . "\r\n";
1285             }
1286             }
1287             }
1288             }
1289             }
1290              
1291              
1292             # Push all messages that can be released at this time into the corresponding char based output for each client
1293 0           foreach my $cid (keys %{$self->{clients}}) {
  0            
1294 0           while(scalar @{$self->{clients}->{$cid}->{outmessages}}) {
  0            
1295 0 0         last if($self->{clients}->{$cid}->{outmessages}->[0]->{releasetime} > $now);
1296              
1297 0           my $outmsg = shift @{$self->{clients}->{$cid}->{outmessages}};
  0            
1298 0 0         if($outmsg->{message} eq 'EXIT') {
1299 0           push @{$self->{toremove}}, $cid; # Disconnect the client
  0            
1300             } else {
1301 0           $self->{clients}->{$cid}->{outbuffer} .= $outmsg->{message} . "\r\n";
1302             }
1303             }
1304             }
1305              
1306 0           return;
1307             }
1308              
1309 0     0     sub _handleMessageOverhead($self, $cid, $inmsg) {
  0            
  0            
  0            
  0            
1310 0           my $now = $self->_getTime();
1311              
1312 0 0         if($inmsg =~ /^OVERHEAD\ (.+?)\ (.+)/) {
1313 0           my ($flags, $value) = ($1, $2);
1314 0           $self->{sendinterclacks} = 0;
1315 0           my @flagparts = split//, $flags;
1316 0           my %parsedflags;
1317             my %newflags;
1318 0           foreach my $key (sort keys %overheadflags) {
1319 0 0         if(contains($key, \@flagparts)) {
1320 0           $parsedflags{$overheadflags{$key}} = 1;
1321 0           $newflags{$overheadflags{$key}} = 1;
1322             } else {
1323 0           $parsedflags{$overheadflags{$key}} = 0;
1324 0           $newflags{$overheadflags{$key}} = 0;
1325             }
1326             }
1327              
1328 0 0         if($parsedflags{auth_token}) {
1329 0 0         if($value eq $self->{authtoken}) {
1330 0           $self->{clients}->{$cid}->{authok} = 1;
1331             #$self->{clients}->{$cid}->{outbuffer} .= "OVERHEAD O Welcome!\r\n";
1332 0           push @{$self->{clients}->{$cid}->{outmessages}}, {releasetime => $now + $self->{clients}->{$cid}->{outmessagedelay}, message => 'OVERHEAD O Welcome!'};
  0            
1333 0           return 1; # NO LOGGING OF CREDENTIALS
1334             } else {
1335 0           $self->{clients}->{$cid}->{authok} = 0;
1336             #$self->{clients}->{$cid}->{outbuffer} .= "OVERHEAD F Login failed!\r\n";
1337 0           push @{$self->{clients}->{$cid}->{outmessages}}, {releasetime => $now + $self->{clients}->{$cid}->{outmessagedelay}, message => 'OVERHEAD F Login failed!'};
  0            
1338 0           push @{$self->{clients}->{$cid}->{outmessages}}, {releasetime => $now + $self->{clients}->{$cid}->{outmessagedelay}, message => 'EXIT'};
  0            
1339 0           push @{$self->{toremove}}, $cid; # Disconnect the client
  0            
1340 0           return 1; # NO LOGGING OF CREDENTIALS
1341             }
1342             }
1343              
1344             # Ignore other command when not authenticated
1345 0 0         if(!$self->{clients}->{$cid}->{authok}) {
1346 0           return 1;
1347             }
1348              
1349 0 0         if($parsedflags{timestamp}) {
1350 0           $now = $self->_getTime(); # Make sure we are at the "latest" $now. This is one of the very few critical sections
1351 0           $self->{clients}->{$cid}->{client_timeoffset} = $now - $value;
1352 0           print "**** CLIENT TIME OFFSET: ", $self->{clients}->{$cid}->{client_timeoffset}, "\n";
1353 0           return 1;
1354             }
1355              
1356 0 0 0       if($parsedflags{lock_for_sync} && $self->{clients}->{$cid}->{interclacksclient}) {
1357 0 0         if($value) {
1358 0           print "Interclacks sync lock ON.\n";
1359 0           $self->{interclackslock} = 1;
1360             } else {
1361 0           print "Interclacks sync lock OFF.\n";
1362 0           $self->{interclackslock} = 0;
1363              
1364             # Send server our keys AFTER we got everything FROM the server (e.g. after unlock)
1365 0           $self->{clients}->{$cid}->{outbuffer} .= "OVERHEAD T " . $self->_getTime() . "\r\n"; # Send local time to server for offset calculation
1366              
1367 0           $now = $self->_getTime();
1368 0           foreach my $ckey (sort keys %{$self->{cache}}) {
  0            
1369             # Sanity checks
1370 0 0 0       if(!defined($self->{cache}->{$ckey}->{cachetime}) || !looks_like_number($self->{cache}->{$ckey}->{cachetime})) {
1371 0           $self->{cache}->{$ckey}->{cachetime} = $now;
1372             }
1373 0 0 0       if(!defined($self->{cache}->{$ckey}->{accesstime}) || !looks_like_number($self->{cache}->{$ckey}->{accesstime})) {
1374 0           $self->{cache}->{$ckey}->{accesstime} = $now;
1375             }
1376 0 0 0       if(!defined($self->{cache}->{$ckey}->{deleted}) || !$self->{cache}->{$ckey}->{deleted}) {
1377 0           $self->{cache}->{$ckey}->{deleted} = 0;
1378             }
1379 0 0         if(!defined($self->{cache}->{$ckey}->{data})) {
1380 0           $self->{cache}->{$ckey}->{data} = '';
1381             }
1382            
1383             # Send KEYSYNC commands
1384 0 0         if(!$self->{cache}->{$ckey}->{deleted}) {
1385 0           $self->{clients}->{$cid}->{outbuffer} .= "KEYSYNC " . $self->{cache}->{$ckey}->{cachetime} . " " . $self->{cache}->{$ckey}->{accesstime} . " U $ckey=" . $self->{cache}->{$ckey}->{data} . "\r\n";
1386             } else {
1387 0           $self->{clients}->{$cid}->{outbuffer} .= "KEYSYNC " . $self->{cache}->{$ckey}->{cachetime} . " 0 D $ckey=REMOVED\r\n";
1388             }
1389             }
1390             }
1391 0           $parsedflags{forward_message} = 0; # Don't forward
1392 0           $newflags{return_to_sender} = 0; # Don't return to sender
1393             }
1394              
1395 0 0 0       if($parsedflags{close_all_connections} && $value) {
1396 0           foreach my $closecid (keys %{$self->{clients}}) {
  0            
1397 0 0 0       if($self->{clients}->{$closecid}->{interclacks} && $parsedflags{forward_message}) {
1398 0           $self->_evalsyswrite($self->{clients}->{$closecid}->{socket}, "\r\nOVERHEAD GC 1\r\n");
1399             }
1400 0           $self->_evalsyswrite($self->{clients}->{$closecid}->{socket}, "\r\nQUIT\r\n");
1401 0           push @{$self->{toremove}}, $closecid;
  0            
1402             }
1403 0           $parsedflags{forward_message} = 0; # Already forwarded where needed
1404             }
1405              
1406 0 0         if($parsedflags{shutdown_service}) {
1407 0           $value = 0 + $value;
1408 0 0         if($value > 0) {
1409 0           $self->{shutdowntime} = $value + $now;
1410 0           print STDERR "Shutting down in $value seconds\n";
1411             }
1412             }
1413 0 0         if($parsedflags{discard_message}) {
1414 0           $self->{discardafterlogging} = 1;
1415             }
1416 0 0         if($parsedflags{no_logging}) {
1417 0           $self->{nodebug} = 1;
1418             }
1419              
1420 0 0         if($parsedflags{error_message}) {
1421 0           print STDERR 'ERROR from ', $cid, ': ', $value, "\n";
1422             }
1423              
1424 0 0         if($parsedflags{set_interclacks_mode}) {
1425 0           $newflags{forward_message} = 0;
1426 0           $newflags{return_to_sender} = 0;
1427              
1428 0 0         if($value) {
1429 0           $self->{clients}->{$cid}->{interclacks} = 1;
1430 0           $self->{clients}->{$cid}->{lastping} = $now;
1431              
1432              
1433             $self->{clients}->{$cid}->{outbuffer} .= "CLACKS PageCamel $VERSION in interclacks master mode\r\n" . # Tell client we are in interclacks master mode
1434             "OVERHEAD M Authentication required\r\n" . # Informal message
1435 0           "OVERHEAD A " . $self->{authtoken} . "\r\n" . # ...and send Auth token...
1436             "OVERHEAD L 1\r\n" . # ...and lock client for sync
1437             "OVERHEAD T " . time . "\r\n"; # ... and send local timestamp
1438              
1439             # Make sure our new interclacks client has an *exact* copy of our buffer
1440             #$self->{clients}->{$cid}->{outbuffer} .= "CLEARCACHE\r\n";
1441            
1442 0           $now = $self->_getTime();
1443 0           foreach my $ckey (sort keys %{$self->{cache}}) {
  0            
1444             # Sanity checks
1445 0 0 0       if(!defined($self->{cache}->{$ckey}->{cachetime}) || !looks_like_number($self->{cache}->{$ckey}->{cachetime})) {
1446 0           $self->{cache}->{$ckey}->{cachetime} = $now;
1447             }
1448 0 0 0       if(!defined($self->{cache}->{$ckey}->{accesstime}) || !looks_like_number($self->{cache}->{$ckey}->{accesstime})) {
1449 0           $self->{cache}->{$ckey}->{accesstime} = $now;
1450             }
1451 0 0 0       if(!defined($self->{cache}->{$ckey}->{deleted}) || !$self->{cache}->{$ckey}->{deleted}) {
1452 0           $self->{cache}->{$ckey}->{deleted} = 0;
1453             }
1454 0 0         if(!defined($self->{cache}->{$ckey}->{data})) {
1455 0           $self->{cache}->{$ckey}->{data} = '';
1456             }
1457              
1458             # Send KEYSYNC commands
1459 0 0         if(!$self->{cache}->{$ckey}->{deleted}) {
1460 0           $self->{clients}->{$cid}->{outbuffer} .= "KEYSYNC " . $self->{cache}->{$ckey}->{cachetime} . " " . $self->{cache}->{$ckey}->{accesstime} . " U $ckey=" . $self->{cache}->{$ckey}->{data} . "\r\n";
1461             } else {
1462 0           $self->{clients}->{$cid}->{outbuffer} .= "KEYSYNC " . $self->{cache}->{$ckey}->{cachetime} . " 0 D $ckey=REMOVED\r\n";
1463             }
1464             }
1465 0           $self->{clients}->{$cid}->{outbuffer} .= "OVERHEAD L 0\r\n"; # unlock client after sync
1466 0           $self->{clients}->{$cid}->{outbuffer} .= "PING\r\n";
1467 0           $self->{clients}->{$cid}->{lastinterclacksping} = $now;
1468             } else {
1469 0           $self->{clients}->{$cid}->{interclacks} = 0;
1470 0           $self->{clients}->{$cid}->{lastping} = $now;
1471             }
1472             }
1473              
1474 0           my $newflagstring = '';
1475 0           $newflags{return_to_sender} = 0;
1476              
1477 0           foreach my $key (sort keys %overheadflags) {
1478 0 0         next if($key eq 'Z');
1479 0 0         if($newflags{$overheadflags{$key}}) {
1480 0           $newflagstring .= $key;
1481             }
1482             }
1483 0 0         if($newflagstring eq '') {
1484 0           $newflagstring = 'Z';
1485             }
1486              
1487 0 0         if($parsedflags{forward_message}) {
1488 0           foreach my $overheadcid (keys %{$self->{clients}}) {
  0            
1489 0 0 0       next if($cid eq $overheadcid && !$parsedflags{return_to_sender});
1490              
1491 0           $self->{clients}->{$overheadcid}->{outbuffer} .= "OVERHEAD $newflagstring $value\r\n";
1492             }
1493             }
1494              
1495 0           return 1;
1496             }
1497              
1498 0           return 0;
1499             }
1500              
1501 0     0     sub _handleMessageCaching($self, $cid, $inmsg) {
  0            
  0            
  0            
  0            
1502 0           my $now = $self->_getTime();
1503              
1504 0 0         if($inmsg =~ /^KEYSYNC\ (.+?)\ (.+?)\ (.+?)\ (.+?)\=(.*)/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1505             #print "***** ", $inmsg, "\n";
1506 0           my ($ctimestamp, $atimestamp, $cmode, $ckey, $cval) = ($1, $2, $3, $4, $5);
1507 0           $self->{clients}->{$cid}->{lastping} = $now; # KEYSYNC acts as a PING as well
1508              
1509 0           $ctimestamp += $self->{clients}->{$cid}->{client_timeoffset}; # Take client time offset into account
1510 0 0         if($atimestamp) {
1511 0           $atimestamp += $self->{clients}->{$cid}->{client_timeoffset}; # Take client time offset into account
1512             }
1513              
1514 0 0 0       if(!defined($self->{cache}->{$ckey}) || $ctimestamp > $self->{cache}->{$ckey}->{cachetime}) {
1515             # If *we* have the older entry (or none at all), *only* then work on the keysync command
1516 0           $self->{cache}->{$ckey} = {
1517             data => $cval,
1518             cachetime => $ctimestamp,
1519             accesstime => $atimestamp,
1520             deleted => 0,
1521             };
1522 0 0         if($cmode eq 'D') {
1523 0           $self->{cache}->{$ckey}->{data} = '';
1524 0           $self->{cache}->{$ckey}->{deleted} = 1;
1525             }
1526             }
1527              
1528 0           $self->{savecache} = 1;
1529 0           $self->{sendinterclacks} = 1;
1530             } elsif($inmsg =~ /^STORE\ (.+?)\=(.*)/) {
1531 0           my ($ckey, $cval) = ($1, $2);
1532 0           $self->{cache}->{$ckey} = {
1533             data => $cval,
1534             cachetime => $now,
1535             accesstime => $now,
1536             deleted => 0,
1537             };
1538 0           $self->{savecache} = 1;
1539             } elsif($inmsg =~ /^SETANDSTORE\ (.+?)\=(.*)/) {
1540 0           my ($ckey, $cval) = ($1, $2);
1541 0           my %tmp = (
1542             sender => $cid,
1543             type => 'SETANDSTORE',
1544             name => $ckey,
1545             value => $cval,
1546             );
1547 0           push @{$self->{outbox}}, \%tmp;
  0            
1548 0           $self->{cache}->{$ckey} = {
1549             data => $cval,
1550             cachetime => $now,
1551             accesstime => $now,
1552             deleted => 0,
1553             };
1554 0           $self->{savecache} = 1;
1555             } elsif($inmsg =~ /^RETRIEVE\ (.+)/) {
1556             #$self->{clients}->{$cid}->{outbuffer} .= "SET ". $line->{name} . "=" . $line->{value} . "\r\n";
1557 0           my $ckey = $1;
1558 0 0 0       if(defined($self->{cache}->{$ckey}) && !$self->{cache}->{$ckey}->{deleted}) {
1559 0           $self->{clients}->{$cid}->{outbuffer} .= "RETRIEVED $ckey=" . $self->{cache}->{$ckey}->{data} . "\r\n";
1560 0           $self->{cache}->{$ckey}->{accesstime} = $now;
1561 0           $self->{savecache} = 1;
1562             } else {
1563 0           $self->{clients}->{$cid}->{outbuffer} .= "NOTRETRIEVED $ckey\r\n";
1564             }
1565 0           $self->{sendinterclacks} = 0;
1566             } elsif($inmsg =~ /^REMOVE\ (.+)/) {
1567 0           my $ckey = $1;
1568 0           $self->{cache}->{$ckey} = {
1569             data => '',
1570             cachetime => $now,
1571             accesstime => $now,
1572             deleted => 1,
1573             };
1574 0           $self->{savecache} = 1;
1575             } elsif($inmsg =~ /^INCREMENT\ (.+)/) {
1576 0           my $ckey = $1;
1577 0           my $cval = 1;
1578 0 0         if($ckey =~ /(.+)\=(.+)/) {
1579 0           ($ckey, $cval) = ($1, $2);
1580 0           $cval = 0 + $cval;
1581             }
1582              
1583 0           my $oldval = 0;
1584 0 0 0       if(defined($self->{cache}->{$ckey}) && !$self->{cache}->{$ckey}->{deleted} && looks_like_number($self->{cache}->{$ckey}->{data})) {
      0        
1585 0           $oldval = $self->{cache}->{$ckey}->{data};
1586             }
1587              
1588 0           $self->{cache}->{$ckey} = {
1589             data => $oldval + $cval,
1590             cachetime => $now,
1591             accesstime => $now,
1592             deleted => 0,
1593             };
1594              
1595 0           $self->{savecache} = 1;
1596             } elsif($inmsg =~ /^DECREMENT\ (.+)/) {
1597 0           my $ckey = $1;
1598 0           my $cval = 1;
1599 0 0         if($ckey =~ /(.+)\=(.+)/) {
1600 0           ($ckey, $cval) = ($1, $2);
1601 0           $cval = 0 + $cval;
1602             }
1603              
1604 0           my $oldval = 0;
1605 0 0 0       if(defined($self->{cache}->{$ckey}) && !$self->{cache}->{$ckey}->{deleted} && looks_like_number($self->{cache}->{$ckey}->{data})) {
      0        
1606 0           $oldval = $self->{cache}->{$ckey}->{data};
1607             }
1608              
1609 0           $self->{cache}->{$ckey} = {
1610             data => $oldval - $cval,
1611             cachetime => $now,
1612             accesstime => $now,
1613             deleted => 0,
1614             };
1615              
1616 0           $self->{savecache} = 1;
1617             } elsif($inmsg =~ /^KEYLIST/) {
1618 0           $self->{clients}->{$cid}->{outbuffer} .= "KEYLISTSTART\r\n";
1619 0           foreach my $ckey (sort keys %{$self->{cache}}) {
  0            
1620 0           $self->{clients}->{$cid}->{outbuffer} .= "KEY $ckey\r\n";
1621             }
1622 0           $self->{clients}->{$cid}->{outbuffer} .= "KEYLISTEND\r\n";
1623 0           $self->{sendinterclacks} = 0;
1624             } elsif($inmsg =~ /^CLEARCACHE/) {
1625 0           $self->{cache} = {};
1626 0           $self->{savecache} = 1;
1627             } else {
1628             # "not handled in this sub"
1629 0           return 0;
1630             }
1631              
1632 0           return 1;
1633             }
1634              
1635 0     0     sub _handleMessageControl($self, $cid, $inmsg) {
  0            
  0            
  0            
  0            
1636 0           my $now = $self->_getTime();
1637              
1638 0 0 0       if($inmsg =~ /^LISTEN\ (.*)/) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1639 0           $self->{clients}->{$cid}->{listening}->{$1} = 1;
1640 0           $self->{sendinterclacks} = 0;
1641             } elsif($inmsg =~ /^UNLISTEN\ (.*)/) {
1642 0           delete $self->{clients}->{$cid}->{listening}->{$1};
1643 0           $self->{sendinterclacks} = 0;
1644             } elsif($inmsg =~ /^MONITOR/) {
1645 0           $self->{clients}->{$cid}->{mirror} = 1;
1646 0           $self->{sendinterclacks} = 0;
1647             } elsif($inmsg =~ /^UNMONITOR/) {
1648 0           $self->{clients}->{$cid}->{mirror} = 0;
1649 0           $self->{sendinterclacks} = 0;
1650             } elsif($inmsg =~ /^QUIT/) {
1651 0           print STDERR "Client disconnected cleanly!\n";
1652 0           push @{$self->{toremove}}, $cid;
  0            
1653 0           $self->{sendinterclacks} = 0;
1654             } elsif($inmsg =~ /^TIMEOUT/ && $self->{clients}->{$cid}->{interclacks}) {
1655 0           print STDERR "Ooops, didn't send timely PINGS through interclacks link!\n";
1656 0           push @{$self->{toremove}}, $cid;
  0            
1657 0           $self->{sendinterclacks} = 0;
1658             } elsif($inmsg =~ /^PING/) {
1659 0           $self->{clients}->{$cid}->{lastping} = $now;
1660 0           $self->{sendinterclacks} = 0;
1661             } elsif($inmsg =~ /^NOPING/) {
1662             # Disable PING check until next PING recieved
1663 0           $self->{clients}->{$cid}->{lastping} = 0;
1664 0           $self->{sendinterclacks} = 0;
1665             } elsif($inmsg =~ /^CLIENTLIST/) {
1666 0           $self->{clients}->{$cid}->{outbuffer} .= "CLIENTLISTSTART\r\n";
1667 0           foreach my $lmccid (sort keys %{$self->{clients}}) {
  0            
1668             $self->{clients}->{$cid}->{outbuffer} .= "CLIENT CID=$lmccid;" .
1669             "HOST=" . $self->{clients}->{$lmccid}->{host} . ";" .
1670             "PORT=" . $self->{clients}->{$lmccid}->{port} . ";" .
1671             "CLIENTINFO=" . $self->{clients}->{$lmccid}->{clientinfo} . ";" .
1672             "OUTBUFFER_LENGTH=" . length($self->{clients}->{$lmccid}->{outbuffer}) . ";" .
1673             "INBUFFER_LENGTH=" . length($self->{clients}->{$lmccid}->{buffer}) . ";" .
1674             "INTERCLACKS=" . $self->{clients}->{$lmccid}->{interclacks} . ";" .
1675             "MONITOR=" . $self->{clients}->{$lmccid}->{mirror} . ";" .
1676             "LASTPING=" . $self->{clients}->{$lmccid}->{lastping} . ";" .
1677 0           "LASTINTERCLACKSPING=" . $self->{clients}->{$lmccid}->{lastinterclacksping} . ";" .
1678             "\r\n";
1679             }
1680 0           $self->{clients}->{$cid}->{outbuffer} .= "CLIENTLISTEND\r\n";
1681 0           $self->{sendinterclacks} = 0;
1682             } elsif($inmsg =~ /^CLIENTDISCONNECT\ (.+)/) {
1683 0           my $lmccid = $1;
1684 0 0         if(defined($self->{clients}->{$lmccid})) {
1685             # Try to notify the client (may or may not work);
1686 0           $self->_evalsyswrite($self->{clients}->{$lmccid}->{socket}, "\r\nQUIT\r\n");
1687 0           push @{$self->{toremove}}, $lmccid;
  0            
1688             }
1689 0           $self->{sendinterclacks} = 0;
1690             } elsif($inmsg =~ /^FLUSH\ (.+)/) {
1691 0           my $retid = $1;
1692 0           $self->{clients}->{$cid}->{outbuffer} .= "FLUSHED $retid\r\n";
1693 0           $self->{sendinterclacks} = 0;
1694             } else {
1695             # "not handled in this sub"
1696 0           return 0;
1697             }
1698              
1699 0           return 1;
1700             }
1701              
1702              
1703 0     0     sub _handleMessageDirect($self, $cid, $inmsg) {
  0            
  0            
  0            
  0            
1704 0 0         if($inmsg =~ /^NOTIFY\ (.*)/) {
    0          
1705 0           my %tmp = (
1706             sender => $cid,
1707             type => 'NOTIFY',
1708             name => $1,
1709             );
1710 0           push @{$self->{outbox}}, \%tmp;
  0            
1711             } elsif($inmsg =~ /^SET\ (.+?)\=(.*)/) {
1712 0           my %tmp = (
1713             sender => $cid,
1714             type => 'SET',
1715             name => $1,
1716             value => $2,
1717             );
1718 0           push @{$self->{outbox}}, \%tmp;
  0            
1719             } else {
1720             # "not handled in this sub"
1721 0           return 0;
1722             }
1723              
1724 0           return 1;
1725             }
1726              
1727             1;
1728             __END__