50709127a1de039652cfa991bdf44108bcfffc14
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $gosa_address;
96 our $no_arp;
97 our $verbose;
98 our $forground;
99 our $cfg_file;
100 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # dak variables
103 our $dak_base_directory;
104 our $dak_signing_keys_directory;
105 our $dak_queue_directory;
106 our $dak_user;
108 # specifies the verbosity of the daemon_log
109 $verbose = 0 ;
111 # if foreground is not null, script will be not forked to background
112 $foreground = 0 ;
114 # specifies the timeout seconds while checking the online status of a registrating client
115 $ping_timeout = 5;
117 $no_arp = 0;
118 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
119 my @packages_list_statements;
120 my $watch_for_new_jobs_in_progress = 0;
122 # holds all incoming decrypted messages
123 our $incoming_db;
124 our $incoming_tn = 'incoming';
125 my $incoming_file_name;
126 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
127 "timestamp DEFAULT 'none'",
128 "headertag DEFAULT 'none'",
129 "targettag DEFAULT 'none'",
130 "xmlmessage DEFAULT 'none'",
131 "module DEFAULT 'none'",
132 "sessionid DEFAULT '0'",
133 );
135 # holds all gosa jobs
136 our $job_db;
137 our $job_queue_tn = 'jobs';
138 my $job_queue_file_name;
139 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
140 "timestamp DEFAULT 'none'",
141 "status DEFAULT 'none'",
142 "result DEFAULT 'none'",
143 "progress DEFAULT 'none'",
144 "headertag DEFAULT 'none'",
145 "targettag DEFAULT 'none'",
146 "xmlmessage DEFAULT 'none'",
147 "macaddress DEFAULT 'none'",
148 "plainname DEFAULT 'none'",
149 "siserver DEFAULT 'none'",
150 "modified DEFAULT '0'",
151 );
153 # holds all other gosa-si-server
154 our $known_server_db;
155 our $known_server_tn = "known_server";
156 my $known_server_file_name;
157 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
159 # holds all registrated clients
160 our $known_clients_db;
161 our $known_clients_tn = "known_clients";
162 my $known_clients_file_name;
163 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
165 # holds all registered clients at a foreign server
166 our $foreign_clients_db;
167 our $foreign_clients_tn = "foreign_clients";
168 my $foreign_clients_file_name;
169 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
171 # holds all logged in user at each client
172 our $login_users_db;
173 our $login_users_tn = "login_users";
174 my $login_users_file_name;
175 my @login_users_col_names = ("client", "user", "timestamp");
177 # holds all fai server, the debian release and tag
178 our $fai_server_db;
179 our $fai_server_tn = "fai_server";
180 my $fai_server_file_name;
181 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
183 our $fai_release_db;
184 our $fai_release_tn = "fai_release";
185 my $fai_release_file_name;
186 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
188 # holds all packages available from different repositories
189 our $packages_list_db;
190 our $packages_list_tn = "packages_list";
191 my $packages_list_file_name;
192 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
193 my $outdir = "/tmp/packages_list_db";
194 my $arch = "i386";
196 # holds all messages which should be delivered to a user
197 our $messaging_db;
198 our $messaging_tn = "messaging";
199 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
200 "flag", "direction", "delivery_time", "message", "timestamp" );
201 my $messaging_file_name;
203 # path to directory to store client install log files
204 our $client_fai_log_dir = "/var/log/fai";
206 # queue which stores taskes until one of the $max_children children are ready to process the task
207 my @tasks = qw();
208 my @msgs_to_decrypt = qw();
209 my $max_children = 2;
212 %cfg_defaults = (
213 "general" => {
214 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
215 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
216 },
217 "server" => {
218 "port" => [\$server_port, "20081"],
219 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
229 "repo-path" => [\$repo_path, '/srv/www/repository'],
230 "ldap-uri" => [\$ldap_uri, ""],
231 "ldap-base" => [\$ldap_base, ""],
232 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
233 "ldap-admin-password" => [\$ldap_admin_password, ""],
234 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
235 "max-clients" => [\$max_clients, 10],
236 "wol-password" => [\$wake_on_lan_passwd, ""],
237 },
238 "GOsaPackages" => {
239 "ip" => [\$gosa_ip, "0.0.0.0"],
240 "port" => [\$gosa_port, "20082"],
241 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
242 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
243 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
244 "key" => [\$GosaPackages_key, "none"],
245 "dak-base" => [\$dak_base_directory, "/srv/archive"],
246 "dak-keyring" => [\$dak_signing_keys_directory, "/srv/archive/keyrings"],
247 "dak-queue" => [\$dak_queue_directory, "/srv/archive/queue"],
248 "dak-user" => [\$dak_user, "deb-dak"],
249 },
250 "ClientPackages" => {
251 "key" => [\$ClientPackages_key, "none"],
252 },
253 "ServerPackages"=> {
254 "address" => [\$foreign_server_string, ""],
255 "domain" => [\$server_domain, ""],
256 "key" => [\$ServerPackages_key, "none"],
257 "key-lifetime" => [\$foreign_servers_register_delay, 120],
258 "job-synchronization" => [\$job_synchronization, "true"],
259 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
260 }
261 );
264 #=== FUNCTION ================================================================
265 # NAME: usage
266 # PARAMETERS: nothing
267 # RETURNS: nothing
268 # DESCRIPTION: print out usage text to STDERR
269 #===============================================================================
270 sub usage {
271 print STDERR << "EOF" ;
272 usage: $prg [-hvf] [-c config]
274 -h : this (help) message
275 -c <file> : config file
276 -f : foreground, process will not be forked to background
277 -v : be verbose (multiple to increase verbosity)
278 -no-arp : starts $prg without connection to arp module
280 EOF
281 print "\n" ;
282 }
285 #=== FUNCTION ================================================================
286 # NAME: read_configfile
287 # PARAMETERS: cfg_file - string -
288 # RETURNS: nothing
289 # DESCRIPTION: read cfg_file and set variables
290 #===============================================================================
291 sub read_configfile {
292 my $cfg;
293 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
294 if( -r $cfg_file ) {
295 $cfg = Config::IniFiles->new( -file => $cfg_file );
296 } else {
297 print STDERR "Couldn't read config file!\n";
298 }
299 } else {
300 $cfg = Config::IniFiles->new() ;
301 }
302 foreach my $section (keys %cfg_defaults) {
303 foreach my $param (keys %{$cfg_defaults{ $section }}) {
304 my $pinfo = $cfg_defaults{ $section }{ $param };
305 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
306 }
307 }
308 }
311 #=== FUNCTION ================================================================
312 # NAME: logging
313 # PARAMETERS: level - string - default 'info'
314 # msg - string -
315 # facility - string - default 'LOG_DAEMON'
316 # RETURNS: nothing
317 # DESCRIPTION: function for logging
318 #===============================================================================
319 sub daemon_log {
320 # log into log_file
321 my( $msg, $level ) = @_;
322 if(not defined $msg) { return }
323 if(not defined $level) { $level = 1 }
324 if(defined $log_file){
325 open(LOG_HANDLE, ">>$log_file");
326 chmod 0600, $log_file;
327 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
328 print STDERR "cannot open $log_file: $!";
329 return
330 }
331 chomp($msg);
332 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
333 if($level <= $verbose){
334 my ($seconds, $minutes, $hours, $monthday, $month,
335 $year, $weekday, $yearday, $sommertime) = localtime(time);
336 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
337 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
338 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
339 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
340 $month = $monthnames[$month];
341 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
342 $year+=1900;
343 my $name = $prg;
345 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
346 print LOG_HANDLE $log_msg;
347 if( $foreground ) {
348 print STDERR $log_msg;
349 }
350 }
351 close( LOG_HANDLE );
352 }
353 }
356 #=== FUNCTION ================================================================
357 # NAME: check_cmdline_param
358 # PARAMETERS: nothing
359 # RETURNS: nothing
360 # DESCRIPTION: validates commandline parameter
361 #===============================================================================
362 sub check_cmdline_param () {
363 my $err_config;
364 my $err_counter = 0;
365 if(not defined($cfg_file)) {
366 $cfg_file = "/etc/gosa-si/server.conf";
367 if(! -r $cfg_file) {
368 $err_config = "please specify a config file";
369 $err_counter += 1;
370 }
371 }
372 if( $err_counter > 0 ) {
373 &usage( "", 1 );
374 if( defined( $err_config)) { print STDERR "$err_config\n"}
375 print STDERR "\n";
376 exit( -1 );
377 }
378 }
381 #=== FUNCTION ================================================================
382 # NAME: check_pid
383 # PARAMETERS: nothing
384 # RETURNS: nothing
385 # DESCRIPTION: handels pid processing
386 #===============================================================================
387 sub check_pid {
388 $pid = -1;
389 # Check, if we are already running
390 if( open(LOCK_FILE, "<$pid_file") ) {
391 $pid = <LOCK_FILE>;
392 if( defined $pid ) {
393 chomp( $pid );
394 if( -f "/proc/$pid/stat" ) {
395 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
396 if( $stat ) {
397 daemon_log("ERROR: Already running",1);
398 close( LOCK_FILE );
399 exit -1;
400 }
401 }
402 }
403 close( LOCK_FILE );
404 unlink( $pid_file );
405 }
407 # create a syslog msg if it is not to possible to open PID file
408 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
409 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
410 if (open(LOCK_FILE, '<', $pid_file)
411 && ($pid = <LOCK_FILE>))
412 {
413 chomp($pid);
414 $msg .= "(PID $pid)\n";
415 } else {
416 $msg .= "(unable to read PID)\n";
417 }
418 if( ! ($foreground) ) {
419 openlog( $0, "cons,pid", "daemon" );
420 syslog( "warning", $msg );
421 closelog();
422 }
423 else {
424 print( STDERR " $msg " );
425 }
426 exit( -1 );
427 }
428 }
430 #=== FUNCTION ================================================================
431 # NAME: import_modules
432 # PARAMETERS: module_path - string - abs. path to the directory the modules
433 # are stored
434 # RETURNS: nothing
435 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
436 # state is on is imported by "require 'file';"
437 #===============================================================================
438 sub import_modules {
439 daemon_log(" ", 1);
441 if (not -e $modules_path) {
442 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
443 }
445 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
446 while (defined (my $file = readdir (DIR))) {
447 if (not $file =~ /(\S*?).pm$/) {
448 next;
449 }
450 my $mod_name = $1;
452 if( $file =~ /ArpHandler.pm/ ) {
453 if( $no_arp > 0 ) {
454 next;
455 }
456 }
458 eval { require $file; };
459 if ($@) {
460 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
461 daemon_log("$@", 5);
462 } else {
463 my $info = eval($mod_name.'::get_module_info()');
464 # Only load module if get_module_info() returns a non-null object
465 if( $info ) {
466 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
467 $known_modules->{$mod_name} = $info;
468 daemon_log("0 INFO: module $mod_name loaded", 5);
469 }
470 }
471 }
472 close (DIR);
473 }
476 #=== FUNCTION ================================================================
477 # NAME: sig_int_handler
478 # PARAMETERS: signal - string - signal arose from system
479 # RETURNS: noting
480 # DESCRIPTION: handels tasks to be done befor signal becomes active
481 #===============================================================================
482 sub sig_int_handler {
483 my ($signal) = @_;
485 # if (defined($ldap_handle)) {
486 # $ldap_handle->disconnect;
487 # }
488 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
491 daemon_log("shutting down gosa-si-server", 1);
492 system("kill `ps -C gosa-si-server -o pid=`");
493 }
494 $SIG{INT} = \&sig_int_handler;
497 sub check_key_and_xml_validity {
498 my ($crypted_msg, $module_key, $session_id) = @_;
499 my $msg;
500 my $msg_hash;
501 my $error_string;
502 eval{
503 $msg = &decrypt_msg($crypted_msg, $module_key);
505 if ($msg =~ /<xml>/i){
506 $msg =~ s/\s+/ /g; # just for better daemon_log
507 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
508 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
510 ##############
511 # check header
512 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
513 my $header_l = $msg_hash->{'header'};
514 if( 1 > @{$header_l} ) { die 'empty header tag'; }
515 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
516 my $header = @{$header_l}[0];
517 if( 0 == length $header) { die 'empty string in header tag'; }
519 ##############
520 # check source
521 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
522 my $source_l = $msg_hash->{'source'};
523 if( 1 > @{$source_l} ) { die 'empty source tag'; }
524 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
525 my $source = @{$source_l}[0];
526 if( 0 == length $source) { die 'source error'; }
528 ##############
529 # check target
530 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
531 my $target_l = $msg_hash->{'target'};
532 if( 1 > @{$target_l} ) { die 'empty target tag'; }
533 }
534 };
535 if($@) {
536 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
537 $msg = undef;
538 $msg_hash = undef;
539 }
541 return ($msg, $msg_hash);
542 }
545 sub check_outgoing_xml_validity {
546 my ($msg, $session_id) = @_;
548 my $msg_hash;
549 eval{
550 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
552 ##############
553 # check header
554 my $header_l = $msg_hash->{'header'};
555 if( 1 != @{$header_l} ) {
556 die 'no or more than one headers specified';
557 }
558 my $header = @{$header_l}[0];
559 if( 0 == length $header) {
560 die 'header has length 0';
561 }
563 ##############
564 # check source
565 my $source_l = $msg_hash->{'source'};
566 if( 1 != @{$source_l} ) {
567 die 'no or more than 1 sources specified';
568 }
569 my $source = @{$source_l}[0];
570 if( 0 == length $source) {
571 die 'source has length 0';
572 }
573 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
574 $source =~ /^GOSA$/i ) {
575 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
576 }
578 ##############
579 # check target
580 my $target_l = $msg_hash->{'target'};
581 if( 0 == @{$target_l} ) {
582 die "no targets specified";
583 }
584 foreach my $target (@$target_l) {
585 if( 0 == length $target) {
586 die "target has length 0";
587 }
588 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
589 $target =~ /^GOSA$/i ||
590 $target =~ /^\*$/ ||
591 $target =~ /KNOWN_SERVER/i ||
592 $target =~ /JOBDB/i ||
593 $target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ){
594 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
595 }
596 }
597 };
598 if($@) {
599 daemon_log("$session_id WARNING: outgoing msg is not gosa-si envelope conform: ", 5);
600 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 5);
601 $msg_hash = undef;
602 }
604 return ($msg_hash);
605 }
608 sub input_from_known_server {
609 my ($input, $remote_ip, $session_id) = @_ ;
610 my ($msg, $msg_hash, $module);
612 my $sql_statement= "SELECT * FROM known_server";
613 my $query_res = $known_server_db->select_dbentry( $sql_statement );
615 while( my ($hit_num, $hit) = each %{ $query_res } ) {
616 my $host_name = $hit->{hostname};
617 if( not $host_name =~ "^$remote_ip") {
618 next;
619 }
620 my $host_key = $hit->{hostkey};
621 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
622 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
624 # check if module can open msg envelope with module key
625 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
626 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
627 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
628 daemon_log("$@", 8);
629 next;
630 }
631 else {
632 $msg = $tmp_msg;
633 $msg_hash = $tmp_msg_hash;
634 $module = "ServerPackages";
635 last;
636 }
637 }
639 if( (!$msg) || (!$msg_hash) || (!$module) ) {
640 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
641 }
643 return ($msg, $msg_hash, $module);
644 }
647 sub input_from_known_client {
648 my ($input, $remote_ip, $session_id) = @_ ;
649 my ($msg, $msg_hash, $module);
651 my $sql_statement= "SELECT * FROM known_clients";
652 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
653 while( my ($hit_num, $hit) = each %{ $query_res } ) {
654 my $host_name = $hit->{hostname};
655 if( not $host_name =~ /^$remote_ip:\d*$/) {
656 next;
657 }
658 my $host_key = $hit->{hostkey};
659 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
660 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
662 # check if module can open msg envelope with module key
663 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
665 if( (!$msg) || (!$msg_hash) ) {
666 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
667 &daemon_log("$@", 8);
668 next;
669 }
670 else {
671 $module = "ClientPackages";
672 last;
673 }
674 }
676 if( (!$msg) || (!$msg_hash) || (!$module) ) {
677 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
678 }
680 return ($msg, $msg_hash, $module);
681 }
684 sub input_from_unknown_host {
685 no strict "refs";
686 my ($input, $session_id) = @_ ;
687 my ($msg, $msg_hash, $module);
688 my $error_string;
690 my %act_modules = %$known_modules;
692 while( my ($mod, $info) = each(%act_modules)) {
694 # check a key exists for this module
695 my $module_key = ${$mod."_key"};
696 if( not defined $module_key ) {
697 if( $mod eq 'ArpHandler' ) {
698 next;
699 }
700 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
701 next;
702 }
703 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
705 # check if module can open msg envelope with module key
706 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
707 if( (not defined $msg) || (not defined $msg_hash) ) {
708 next;
709 }
710 else {
711 $module = $mod;
712 last;
713 }
714 }
716 if( (!$msg) || (!$msg_hash) || (!$module)) {
717 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
718 }
720 return ($msg, $msg_hash, $module);
721 }
724 sub create_ciphering {
725 my ($passwd) = @_;
726 if((!defined($passwd)) || length($passwd)==0) {
727 $passwd = "";
728 }
729 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
730 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
731 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
732 $my_cipher->set_iv($iv);
733 return $my_cipher;
734 }
737 sub encrypt_msg {
738 my ($msg, $key) = @_;
739 my $my_cipher = &create_ciphering($key);
740 my $len;
741 {
742 use bytes;
743 $len= 16-length($msg)%16;
744 }
745 $msg = "\0"x($len).$msg;
746 $msg = $my_cipher->encrypt($msg);
747 chomp($msg = &encode_base64($msg));
748 # there are no newlines allowed inside msg
749 $msg=~ s/\n//g;
750 return $msg;
751 }
754 sub decrypt_msg {
756 my ($msg, $key) = @_ ;
757 $msg = &decode_base64($msg);
758 my $my_cipher = &create_ciphering($key);
759 $msg = $my_cipher->decrypt($msg);
760 $msg =~ s/\0*//g;
761 return $msg;
762 }
765 sub get_encrypt_key {
766 my ($target) = @_ ;
767 my $encrypt_key;
768 my $error = 0;
770 # target can be in known_server
771 if( not defined $encrypt_key ) {
772 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
773 my $query_res = $known_server_db->select_dbentry( $sql_statement );
774 while( my ($hit_num, $hit) = each %{ $query_res } ) {
775 my $host_name = $hit->{hostname};
776 if( $host_name ne $target ) {
777 next;
778 }
779 $encrypt_key = $hit->{hostkey};
780 last;
781 }
782 }
784 # target can be in known_client
785 if( not defined $encrypt_key ) {
786 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
787 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
788 while( my ($hit_num, $hit) = each %{ $query_res } ) {
789 my $host_name = $hit->{hostname};
790 if( $host_name ne $target ) {
791 next;
792 }
793 $encrypt_key = $hit->{hostkey};
794 last;
795 }
796 }
798 return $encrypt_key;
799 }
802 #=== FUNCTION ================================================================
803 # NAME: open_socket
804 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
805 # [PeerPort] string necessary if port not appended by PeerAddr
806 # RETURNS: socket IO::Socket::INET
807 # DESCRIPTION: open a socket to PeerAddr
808 #===============================================================================
809 sub open_socket {
810 my ($PeerAddr, $PeerPort) = @_ ;
811 if(defined($PeerPort)){
812 $PeerAddr = $PeerAddr.":".$PeerPort;
813 }
814 my $socket;
815 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
816 Porto => "tcp",
817 Type => SOCK_STREAM,
818 Timeout => 5,
819 );
820 if(not defined $socket) {
821 return;
822 }
823 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
824 return $socket;
825 }
828 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
829 #=== FUNCTION ================================================================
830 # NAME: get_ip
831 # PARAMETERS: interface name (i.e. eth0)
832 # RETURNS: (ip address)
833 # DESCRIPTION: Uses ioctl to get ip address directly from system.
834 #===============================================================================
835 #sub get_ip {
836 # my $ifreq= shift;
837 # my $result= "";
838 # my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
839 # my $proto= getprotobyname('ip');
840 #
841 # socket SOCKET, PF_INET, SOCK_DGRAM, $proto
842 # or die "socket: $!";
843 #
844 # if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
845 # my ($if, $sin) = unpack 'a16 a16', $ifreq;
846 # my ($port, $addr) = sockaddr_in $sin;
847 # my $ip = inet_ntoa $addr;
848 #
849 # if ($ip && length($ip) > 0) {
850 # $result = $ip;
851 # }
852 # }
853 #
854 # return $result;
855 #}
858 sub get_local_ip_for_remote_ip {
859 my $remote_ip= shift;
860 my $result="0.0.0.0";
862 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
863 if($remote_ip eq "127.0.0.1") {
864 $result = "127.0.0.1";
865 } else {
866 my $PROC_NET_ROUTE= ('/proc/net/route');
868 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
869 or die "Could not open $PROC_NET_ROUTE";
871 my @ifs = <PROC_NET_ROUTE>;
873 close(PROC_NET_ROUTE);
875 # Eat header line
876 shift @ifs;
877 chomp @ifs;
878 foreach my $line(@ifs) {
879 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
880 my $destination;
881 my $mask;
882 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
883 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
884 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
885 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
886 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
887 # destination matches route, save mac and exit
888 $result= &get_ip($Iface);
889 last;
890 }
891 }
892 }
893 } else {
894 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
895 }
896 return $result;
897 }
900 sub send_msg_to_target {
901 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
902 my $error = 0;
903 my $header;
904 my $timestamp = &get_time();
905 my $new_status;
906 my $act_status;
907 my ($sql_statement, $res);
909 if( $msg_header ) {
910 $header = "'$msg_header'-";
911 } else {
912 $header = "";
913 }
915 # Patch the source ip
916 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
917 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
918 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
919 }
921 # encrypt xml msg
922 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
924 # opensocket
925 my $socket = &open_socket($address);
926 if( !$socket ) {
927 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
928 $error++;
929 }
931 if( $error == 0 ) {
932 # send xml msg
933 print $socket $crypted_msg."\n";
935 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
936 daemon_log("$session_id DEBUG: message:\n$msg", 9);
938 }
940 # close socket in any case
941 if( $socket ) {
942 close $socket;
943 }
945 if( $error > 0 ) { $new_status = "down"; }
946 else { $new_status = $msg_header; }
949 # known_clients
950 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
951 $res = $known_clients_db->select_dbentry($sql_statement);
952 if( keys(%$res) == 1) {
953 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
954 if ($act_status eq "down" && $new_status eq "down") {
955 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
956 $res = $known_clients_db->del_dbentry($sql_statement);
957 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
958 } else {
959 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
960 $res = $known_clients_db->update_dbentry($sql_statement);
961 if($new_status eq "down"){
962 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
963 } else {
964 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
965 }
966 }
967 }
969 # known_server
970 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
971 $res = $known_server_db->select_dbentry($sql_statement);
972 if( keys(%$res) == 1) {
973 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
974 if ($act_status eq "down" && $new_status eq "down") {
975 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
976 $res = $known_server_db->del_dbentry($sql_statement);
977 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
978 }
979 else {
980 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
981 $res = $known_server_db->update_dbentry($sql_statement);
982 if($new_status eq "down"){
983 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
984 } else {
985 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
986 }
987 }
988 }
989 return $error;
990 }
993 sub update_jobdb_status_for_send_msgs {
994 my ($answer, $error) = @_;
995 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
996 my $jobdb_id = $1;
998 # sending msg faild
999 if( $error ) {
1000 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1001 my $sql_statement = "UPDATE $job_queue_tn ".
1002 "SET status='error', result='can not deliver msg, please consult log file' ".
1003 "WHERE id=$jobdb_id";
1004 my $res = $job_db->update_dbentry($sql_statement);
1005 }
1007 # sending msg was successful
1008 } else {
1009 my $sql_statement = "UPDATE $job_queue_tn ".
1010 "SET status='done' ".
1011 "WHERE id=$jobdb_id AND status='processed'";
1012 my $res = $job_db->update_dbentry($sql_statement);
1013 }
1014 }
1015 }
1018 sub sig_handler {
1019 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1020 daemon_log("0 INFO got signal '$signal'", 1);
1021 $kernel->sig_handled();
1022 return;
1023 }
1026 sub msg_to_decrypt {
1027 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1028 my $session_id = $session->ID;
1029 my ($msg, $msg_hash, $module);
1030 my $error = 0;
1032 # hole neue msg aus @msgs_to_decrypt
1033 my $next_msg = shift @msgs_to_decrypt;
1035 # entschlüssle sie
1037 # msg is from a new client or gosa
1038 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1039 # msg is from a gosa-si-server
1040 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1042 }
1043 # msg is from a gosa-si-client
1044 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1045 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1046 }
1047 # an error occurred
1048 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1050 # could not understand a msg from its server the client cause a re-registering process
1051 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1052 "' to cause a re-registering of the client if necessary", 5);
1053 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1054 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1055 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1056 my $host_name = $hit->{'hostname'};
1057 my $host_key = $hit->{'hostkey'};
1058 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1059 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1060 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1061 }
1062 $error++;
1063 }
1066 my $header;
1067 my $target;
1068 my $source;
1069 my $done = 0;
1070 my $sql;
1071 my $res;
1073 # check whether this message should be processed here
1074 if ($error == 0) {
1075 $header = @{$msg_hash->{'header'}}[0];
1076 $target = @{$msg_hash->{'target'}}[0];
1077 $source = @{$msg_hash->{'source'}}[0];
1078 my $not_found_in_known_clients_db = 0;
1079 my $not_found_in_known_server_db = 0;
1080 my $not_found_in_foreign_clients_db = 0;
1081 my $local_address;
1082 my ($target_ip, $target_port) = split(':', $target);
1083 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1084 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1085 } else {
1086 $local_address = $server_address;
1087 }
1089 # target and source is equal to GOSA -> process here
1090 if (not $done) {
1091 if ($target eq "GOSA" && $source eq "GOSA") {
1092 $done = 1;
1093 }
1094 }
1096 # target is own address without forward_to_gosa-tag -> process here
1097 if (not $done) {
1098 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1099 $done = 1;
1100 if ($source eq "GOSA") {
1101 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1102 }
1103 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1104 }
1105 }
1107 # target is a client address in known_clients -> process here
1108 if (not $done) {
1109 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1110 $res = $known_clients_db->select_dbentry($sql);
1111 if (keys(%$res) > 0) {
1112 $done = 1;
1113 my $hostname = $res->{1}->{'hostname'};
1114 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1115 #print STDERR "target is a client address in known_clients -> process here\n";
1116 } else {
1117 $not_found_in_known_clients_db = 1;
1118 }
1119 }
1121 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1122 if (not $done) {
1123 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1124 my $gosa_at;
1125 my $gosa_session_id;
1126 if (($target eq $local_address) && (defined $forward_to_gosa)){
1127 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1128 if ($gosa_at ne $local_address) {
1129 $done = 1;
1130 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1131 }
1132 }
1133 }
1135 # if message should be processed here -> add message to incoming_db
1136 if ($done) {
1137 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1138 # so gosa-si-server knows how to process this kind of messages
1139 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1140 $module = "GosaPackages";
1141 }
1143 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1144 primkey=>[],
1145 headertag=>$header,
1146 targettag=>$target,
1147 xmlmessage=>&encode_base64($msg),
1148 timestamp=>&get_time,
1149 module=>$module,
1150 sessionid=>$session_id,
1151 } );
1152 }
1154 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1155 if (not $done) {
1156 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1157 my $gosa_at;
1158 my $gosa_session_id;
1159 if (($target eq $local_address) && (defined $forward_to_gosa)){
1160 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1161 if ($gosa_at eq $local_address) {
1162 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1163 if( defined $session_reference ) {
1164 $heap = $session_reference->get_heap();
1165 }
1166 if(exists $heap->{'client'}) {
1167 $msg = &encrypt_msg($msg, $GosaPackages_key);
1168 $heap->{'client'}->put($msg);
1169 }
1170 $done = 1;
1171 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1172 }
1173 }
1175 }
1177 # target is a client address in foreign_clients -> forward to registration server
1178 if (not $done) {
1179 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1180 $res = $foreign_clients_db->select_dbentry($sql);
1181 if (keys(%$res) > 0) {
1182 my $hostname = $res->{1}->{'hostname'};
1183 my ($host_ip, $host_port) = split(/:/, $hostname);
1184 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1185 my $regserver = $res->{1}->{'regserver'};
1186 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1187 my $res = $known_server_db->select_dbentry($sql);
1188 if (keys(%$res) > 0) {
1189 my $regserver_key = $res->{1}->{'hostkey'};
1190 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1191 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1192 if ($source eq "GOSA") {
1193 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1194 }
1195 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1196 }
1197 $done = 1;
1198 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1199 } else {
1200 $not_found_in_foreign_clients_db = 1;
1201 }
1202 }
1204 # target is a server address -> forward to server
1205 if (not $done) {
1206 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1207 $res = $known_server_db->select_dbentry($sql);
1208 if (keys(%$res) > 0) {
1209 my $hostkey = $res->{1}->{'hostkey'};
1211 if ($source eq "GOSA") {
1212 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1213 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1215 }
1217 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1218 $done = 1;
1219 #print STDERR "target is a server address -> forward to server\n";
1220 } else {
1221 $not_found_in_known_server_db = 1;
1222 }
1223 }
1226 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1227 if ( $not_found_in_foreign_clients_db
1228 && $not_found_in_known_server_db
1229 && $not_found_in_known_clients_db) {
1230 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1231 primkey=>[],
1232 headertag=>$header,
1233 targettag=>$target,
1234 xmlmessage=>&encode_base64($msg),
1235 timestamp=>&get_time,
1236 module=>$module,
1237 sessionid=>$session_id,
1238 } );
1239 $done = 1;
1240 }
1243 if (not $done) {
1244 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1245 if ($source eq "GOSA") {
1246 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1247 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1249 my $session_reference = $kernel->ID_id_to_session($session_id);
1250 if( defined $session_reference ) {
1251 $heap = $session_reference->get_heap();
1252 }
1253 if(exists $heap->{'client'}) {
1254 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1255 $heap->{'client'}->put($error_msg);
1256 }
1257 }
1258 }
1260 }
1262 return;
1263 }
1266 sub next_task {
1267 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1268 my $running_task = POE::Wheel::Run->new(
1269 Program => sub { process_task($session, $heap, $task) },
1270 StdioFilter => POE::Filter::Reference->new(),
1271 StdoutEvent => "task_result",
1272 StderrEvent => "task_debug",
1273 CloseEvent => "task_done",
1274 );
1275 $heap->{task}->{ $running_task->ID } = $running_task;
1276 }
1278 sub handle_task_result {
1279 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1280 my $client_answer = $result->{'answer'};
1281 if( $client_answer =~ s/session_id=(\d+)$// ) {
1282 my $session_id = $1;
1283 if( defined $session_id ) {
1284 my $session_reference = $kernel->ID_id_to_session($session_id);
1285 if( defined $session_reference ) {
1286 $heap = $session_reference->get_heap();
1287 }
1288 }
1290 if(exists $heap->{'client'}) {
1291 $heap->{'client'}->put($client_answer);
1292 }
1293 }
1294 $kernel->sig(CHLD => "child_reap");
1295 }
1297 sub handle_task_debug {
1298 my $result = $_[ARG0];
1299 print STDERR "$result\n";
1300 }
1302 sub handle_task_done {
1303 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1304 delete $heap->{task}->{$task_id};
1305 }
1307 sub process_task {
1308 no strict "refs";
1309 my ($session, $heap, $task) = @_;
1310 my $error = 0;
1311 my $answer_l;
1312 my ($answer_header, @answer_target_l, $answer_source);
1313 my $client_answer = "";
1315 # prepare all variables needed to process message
1316 #my $msg = $task->{'xmlmessage'};
1317 my $msg = &decode_base64($task->{'xmlmessage'});
1318 my $incoming_id = $task->{'id'};
1319 my $module = $task->{'module'};
1320 my $header = $task->{'headertag'};
1321 my $session_id = $task->{'sessionid'};
1322 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1323 my $source = @{$msg_hash->{'source'}}[0];
1325 # set timestamp of incoming client uptodate, so client will not
1326 # be deleted from known_clients because of expiration
1327 my $act_time = &get_time();
1328 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1329 my $res = $known_clients_db->exec_statement($sql);
1331 ######################
1332 # process incoming msg
1333 if( $error == 0) {
1334 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1335 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1336 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1338 if ( 0 < @{$answer_l} ) {
1339 my $answer_str = join("\n", @{$answer_l});
1340 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1341 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1342 }
1343 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1344 } else {
1345 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1346 }
1348 }
1349 if( !$answer_l ) { $error++ };
1351 ########
1352 # answer
1353 if( $error == 0 ) {
1355 foreach my $answer ( @{$answer_l} ) {
1356 # check outgoing msg to xml validity
1357 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1358 if( not defined $answer_hash ) { next; }
1360 $answer_header = @{$answer_hash->{'header'}}[0];
1361 @answer_target_l = @{$answer_hash->{'target'}};
1362 $answer_source = @{$answer_hash->{'source'}}[0];
1364 # deliver msg to all targets
1365 foreach my $answer_target ( @answer_target_l ) {
1367 # targets of msg are all gosa-si-clients in known_clients_db
1368 if( $answer_target eq "*" ) {
1369 # answer is for all clients
1370 my $sql_statement= "SELECT * FROM known_clients";
1371 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1372 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1373 my $host_name = $hit->{hostname};
1374 my $host_key = $hit->{hostkey};
1375 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1376 &update_jobdb_status_for_send_msgs($answer, $error);
1377 }
1378 }
1380 # targets of msg are all gosa-si-server in known_server_db
1381 elsif( $answer_target eq "KNOWN_SERVER" ) {
1382 # answer is for all server in known_server
1383 my $sql_statement= "SELECT * FROM $known_server_tn";
1384 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1385 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1386 my $host_name = $hit->{hostname};
1387 my $host_key = $hit->{hostkey};
1388 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1389 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1390 &update_jobdb_status_for_send_msgs($answer, $error);
1391 }
1392 }
1394 # target of msg is GOsa
1395 elsif( $answer_target eq "GOSA" ) {
1396 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1397 my $add_on = "";
1398 if( defined $session_id ) {
1399 $add_on = ".session_id=$session_id";
1400 }
1401 # answer is for GOSA and has to returned to connected client
1402 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1403 $client_answer = $gosa_answer.$add_on;
1404 }
1406 # target of msg is job queue at this host
1407 elsif( $answer_target eq "JOBDB") {
1408 $answer =~ /<header>(\S+)<\/header>/;
1409 my $header;
1410 if( defined $1 ) { $header = $1; }
1411 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1412 &update_jobdb_status_for_send_msgs($answer, $error);
1413 }
1415 # target of msg is a mac address
1416 elsif( $answer_target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ) {
1417 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1418 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1419 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1420 my $found_ip_flag = 0;
1421 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1422 my $host_name = $hit->{hostname};
1423 my $host_key = $hit->{hostkey};
1424 $answer =~ s/$answer_target/$host_name/g;
1425 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1426 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1427 &update_jobdb_status_for_send_msgs($answer, $error);
1428 $found_ip_flag++ ;
1429 }
1430 if( $found_ip_flag == 0) {
1431 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1432 }
1434 # answer is for one specific host
1435 } else {
1436 # get encrypt_key
1437 my $encrypt_key = &get_encrypt_key($answer_target);
1438 if( not defined $encrypt_key ) {
1439 # unknown target
1440 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1441 next;
1442 }
1443 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1444 &update_jobdb_status_for_send_msgs($answer, $error);
1445 }
1446 }
1447 }
1448 }
1450 my $filter = POE::Filter::Reference->new();
1451 my %result = (
1452 status => "seems ok to me",
1453 answer => $client_answer,
1454 );
1456 my $output = $filter->put( [ \%result ] );
1457 print @$output;
1460 }
1462 sub session_start {
1463 my ($kernel) = $_[KERNEL];
1464 $global_kernel = $kernel;
1465 $kernel->yield('register_at_foreign_servers');
1466 $kernel->yield('create_fai_server_db', $fai_server_tn );
1467 $kernel->yield('create_fai_release_db', $fai_release_tn );
1468 $kernel->yield('watch_for_next_tasks');
1469 $kernel->sig(USR1 => "sig_handler");
1470 $kernel->sig(USR2 => "recreate_packages_db");
1471 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1472 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1473 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1474 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1475 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1476 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1477 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1479 }
1482 sub watch_for_done_jobs {
1483 my ($kernel,$heap) = @_[KERNEL, HEAP];
1485 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1486 my $res = $job_db->select_dbentry( $sql_statement );
1488 while( my ($id, $hit) = each %{$res} ) {
1489 my $jobdb_id = $hit->{id};
1490 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1491 my $res = $job_db->del_dbentry($sql_statement);
1492 }
1494 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1495 }
1498 # if a job got an update or was modified anyway, send to all other si-server an update message
1499 # of this jobs
1500 sub watch_for_modified_jobs {
1501 my ($kernel,$heap) = @_[KERNEL, HEAP];
1503 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1504 my $res = $job_db->select_dbentry( $sql_statement );
1506 # if db contains no jobs which should be update, do nothing
1507 if (keys %$res != 0) {
1509 # make out of the db result a gosa-si message
1510 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1512 # update all other SI-server
1513 &inform_all_other_si_server($update_msg);
1515 # # determine all other si-server a foreign_job_updates message should be send
1516 # my $sql_statement= "SELECT * FROM $known_server_tn";
1517 # my $res = $known_server_db->select_dbentry( $sql_statement );
1518 # while( my ($hit_num, $hit) = each %$res ) {
1519 # my $act_update_msg = $update_msg;
1520 # my $act_target_address = $hit->{hostname};
1521 # my $act_target_key = $hit->{hostkey};
1522 # my ($act_target_ip, $act_target_port) = split(/:/, $act_target_address);
1523 # my $act_source_address = &get_local_ip_for_remote_ip($act_target_ip).":$act_target_port";
1524 #
1525 # $act_update_msg =~ s/<target>KNOWN_SERVER<\/target>/<target>$act_target_address<\/target>/g;
1526 # $act_update_msg =~ s/<source>MY_LOCAL_ADDRESS<\/source>/<source>$act_source_address<\/source>/g;
1527 # &send_msg_to_target($act_update_msg, $act_target_address, $act_target_key, "foreign_job_updates" , "J");
1528 # }
1530 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1531 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1532 $res = $job_db->update_dbentry($sql_statement);
1533 }
1535 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1536 }
1539 sub watch_for_new_jobs {
1540 if($watch_for_new_jobs_in_progress == 0) {
1541 $watch_for_new_jobs_in_progress = 1;
1542 my ($kernel,$heap) = @_[KERNEL, HEAP];
1544 # check gosa job quaeue for jobs with executable timestamp
1545 my $timestamp = &get_time();
1546 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1547 my $res = $job_db->exec_statement( $sql_statement );
1549 # Merge all new jobs that would do the same actions
1550 my @drops;
1551 my $hits;
1552 foreach my $hit (reverse @{$res} ) {
1553 my $macaddress= lc @{$hit}[8];
1554 my $headertag= @{$hit}[5];
1555 if(
1556 defined($hits->{$macaddress}) &&
1557 defined($hits->{$macaddress}->{$headertag}) &&
1558 defined($hits->{$macaddress}->{$headertag}[0])
1559 ) {
1560 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1561 }
1562 $hits->{$macaddress}->{$headertag}= $hit;
1563 }
1565 # Delete new jobs with a matching job in state 'processing'
1566 foreach my $macaddress (keys %{$hits}) {
1567 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1568 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1569 if(defined($jobdb_id)) {
1570 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1571 my $res = $job_db->exec_statement( $sql_statement );
1572 foreach my $hit (@{$res}) {
1573 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1574 }
1575 } else {
1576 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1577 }
1578 }
1579 }
1581 # Commit deletion
1582 $job_db->exec_statementlist(\@drops);
1584 # Look for new jobs that could be executed
1585 foreach my $macaddress (keys %{$hits}) {
1587 # Look if there is an executing job
1588 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1589 my $res = $job_db->exec_statement( $sql_statement );
1591 # Skip new jobs for host if there is a processing job
1592 if(defined($res) and defined @{$res}[0]) {
1593 next;
1594 }
1596 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1597 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1598 if(defined($jobdb_id)) {
1599 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1601 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1602 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1603 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1605 # expect macaddress is unique!!!!!!
1606 my $target = $res_hash->{1}->{hostname};
1608 # change header
1609 $job_msg =~ s/<header>job_/<header>gosa_/;
1611 # add sqlite_id
1612 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1614 $job_msg =~ /<header>(\S+)<\/header>/;
1615 my $header = $1 ;
1616 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1618 # update status in job queue to 'processing'
1619 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1620 my $res = $job_db->update_dbentry($sql_statement);
1621 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1623 # We don't want parallel processing
1624 last;
1625 }
1626 }
1627 }
1629 $watch_for_new_jobs_in_progress = 0;
1630 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1631 }
1632 }
1636 sub watch_for_new_messages {
1637 my ($kernel,$heap) = @_[KERNEL, HEAP];
1638 my @coll_user_msg; # collection list of outgoing messages
1640 # check messaging_db for new incoming messages with executable timestamp
1641 my $timestamp = &get_time();
1642 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1643 my $res = $messaging_db->exec_statement( $sql_statement );
1644 foreach my $hit (@{$res}) {
1646 # create outgoing messages
1647 my $message_to = @{$hit}[3];
1648 # translate message_to to plain login name
1649 my @message_to_l = split(/,/, $message_to);
1650 my %receiver_h;
1651 foreach my $receiver (@message_to_l) {
1652 if ($receiver =~ /^u_([\s\S]*)$/) {
1653 $receiver_h{$1} = 0;
1654 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1655 my $group_name = $1;
1656 # fetch all group members from ldap and add them to receiver hash
1657 my $ldap_handle = &get_ldap_handle();
1658 if (defined $ldap_handle) {
1659 my $mesg = $ldap_handle->search(
1660 base => $ldap_base,
1661 scope => 'sub',
1662 attrs => ['memberUid'],
1663 filter => "cn=$group_name",
1664 );
1665 if ($mesg->count) {
1666 my @entries = $mesg->entries;
1667 foreach my $entry (@entries) {
1668 my @receivers= $entry->get_value("memberUid");
1669 foreach my $receiver (@receivers) {
1670 $receiver_h{$1} = 0;
1671 }
1672 }
1673 }
1674 # translating errors ?
1675 if ($mesg->code) {
1676 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1677 }
1678 # ldap handle error ?
1679 } else {
1680 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1681 }
1682 } else {
1683 my $sbjct = &encode_base64(@{$hit}[1]);
1684 my $msg = &encode_base64(@{$hit}[7]);
1685 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1686 }
1687 }
1688 my @receiver_l = keys(%receiver_h);
1690 my $message_id = @{$hit}[0];
1692 #add each outgoing msg to messaging_db
1693 my $receiver;
1694 foreach $receiver (@receiver_l) {
1695 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1696 "VALUES ('".
1697 $message_id."', '". # id
1698 @{$hit}[1]."', '". # subject
1699 @{$hit}[2]."', '". # message_from
1700 $receiver."', '". # message_to
1701 "none"."', '". # flag
1702 "out"."', '". # direction
1703 @{$hit}[6]."', '". # delivery_time
1704 @{$hit}[7]."', '". # message
1705 $timestamp."'". # timestamp
1706 ")";
1707 &daemon_log("M DEBUG: $sql_statement", 1);
1708 my $res = $messaging_db->exec_statement($sql_statement);
1709 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1710 }
1712 # set incoming message to flag d=deliverd
1713 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1714 &daemon_log("M DEBUG: $sql_statement", 7);
1715 $res = $messaging_db->update_dbentry($sql_statement);
1716 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1717 }
1719 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1720 return;
1721 }
1723 sub watch_for_delivery_messages {
1724 my ($kernel, $heap) = @_[KERNEL, HEAP];
1726 # select outgoing messages
1727 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1728 #&daemon_log("0 DEBUG: $sql", 7);
1729 my $res = $messaging_db->exec_statement( $sql_statement );
1731 # build out msg for each usr
1732 foreach my $hit (@{$res}) {
1733 my $receiver = @{$hit}[3];
1734 my $msg_id = @{$hit}[0];
1735 my $subject = @{$hit}[1];
1736 my $message = @{$hit}[7];
1738 # resolve usr -> host where usr is logged in
1739 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1740 #&daemon_log("0 DEBUG: $sql", 7);
1741 my $res = $login_users_db->exec_statement($sql);
1743 # reciver is logged in nowhere
1744 if (not ref(@$res[0]) eq "ARRAY") { next; }
1746 my $send_succeed = 0;
1747 foreach my $hit (@$res) {
1748 my $receiver_host = @$hit[0];
1749 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1751 # fetch key to encrypt msg propperly for usr/host
1752 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1753 &daemon_log("0 DEBUG: $sql", 7);
1754 my $res = $known_clients_db->exec_statement($sql);
1756 # host is already down
1757 if (not ref(@$res[0]) eq "ARRAY") { next; }
1759 # host is on
1760 my $receiver_key = @{@{$res}[0]}[2];
1761 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1762 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1763 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1764 if ($error == 0 ) {
1765 $send_succeed++ ;
1766 }
1767 }
1769 if ($send_succeed) {
1770 # set outgoing msg at db to deliverd
1771 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1772 &daemon_log("0 DEBUG: $sql", 7);
1773 my $res = $messaging_db->exec_statement($sql);
1774 }
1775 }
1777 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1778 return;
1779 }
1782 sub watch_for_done_messages {
1783 my ($kernel,$heap) = @_[KERNEL, HEAP];
1785 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1786 #&daemon_log("0 DEBUG: $sql", 7);
1787 my $res = $messaging_db->exec_statement($sql);
1789 foreach my $hit (@{$res}) {
1790 my $msg_id = @{$hit}[0];
1792 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1793 #&daemon_log("0 DEBUG: $sql", 7);
1794 my $res = $messaging_db->exec_statement($sql);
1796 # not all usr msgs have been seen till now
1797 if ( ref(@$res[0]) eq "ARRAY") { next; }
1799 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1800 #&daemon_log("0 DEBUG: $sql", 7);
1801 $res = $messaging_db->exec_statement($sql);
1803 }
1805 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1806 return;
1807 }
1810 sub watch_for_old_known_clients {
1811 my ($kernel,$heap) = @_[KERNEL, HEAP];
1813 my $sql_statement = "SELECT * FROM $known_clients_tn";
1814 my $res = $known_clients_db->select_dbentry( $sql_statement );
1816 my $act_time = int(&get_time());
1818 while ( my ($hit_num, $hit) = each %$res) {
1819 my $expired_timestamp = int($hit->{'timestamp'});
1820 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1821 my $dt = DateTime->new( year => $1,
1822 month => $2,
1823 day => $3,
1824 hour => $4,
1825 minute => $5,
1826 second => $6,
1827 );
1829 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1830 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1831 if ($act_time > $expired_timestamp) {
1832 my $hostname = $hit->{'hostname'};
1833 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1834 my $del_res = $known_clients_db->exec_statement($del_sql);
1836 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1837 }
1839 }
1841 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1842 }
1845 sub watch_for_next_tasks {
1846 my ($kernel,$heap) = @_[KERNEL, HEAP];
1848 my $sql = "SELECT * FROM $incoming_tn";
1849 my $res = $incoming_db->select_dbentry($sql);
1851 while ( my ($hit_num, $hit) = each %$res) {
1852 my $headertag = $hit->{'headertag'};
1853 if ($headertag =~ /^answer_(\d+)/) {
1854 # do not start processing, this message is for a still running POE::Wheel
1855 next;
1856 }
1857 my $message_id = $hit->{'id'};
1858 $kernel->yield('next_task', $hit);
1860 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1861 my $res = $incoming_db->exec_statement($sql);
1862 }
1864 $kernel->delay_set('watch_for_next_tasks', 0.1);
1865 }
1868 sub get_ldap_handle {
1869 my ($session_id) = @_;
1870 my $heap;
1871 my $ldap_handle;
1873 if (not defined $session_id ) { $session_id = 0 };
1874 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1876 if ($session_id == 0) {
1877 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1878 $ldap_handle = Net::LDAP->new( $ldap_uri );
1879 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1881 } else {
1882 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1883 if( defined $session_reference ) {
1884 $heap = $session_reference->get_heap();
1885 }
1887 if (not defined $heap) {
1888 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1889 return;
1890 }
1892 # TODO: This "if" is nonsense, because it doesn't prove that the
1893 # used handle is still valid - or if we've to reconnect...
1894 #if (not exists $heap->{ldap_handle}) {
1895 $ldap_handle = Net::LDAP->new( $ldap_uri );
1896 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1897 $heap->{ldap_handle} = $ldap_handle;
1898 #}
1899 }
1900 return $ldap_handle;
1901 }
1904 sub change_fai_state {
1905 my ($st, $targets, $session_id) = @_;
1906 $session_id = 0 if not defined $session_id;
1907 # Set FAI state to localboot
1908 my %mapActions= (
1909 reboot => '',
1910 update => 'softupdate',
1911 localboot => 'localboot',
1912 reinstall => 'install',
1913 rescan => '',
1914 wake => '',
1915 memcheck => 'memcheck',
1916 sysinfo => 'sysinfo',
1917 install => 'install',
1918 );
1920 # Return if this is unknown
1921 if (!exists $mapActions{ $st }){
1922 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1923 return;
1924 }
1926 my $state= $mapActions{ $st };
1928 my $ldap_handle = &get_ldap_handle($session_id);
1929 if( defined($ldap_handle) ) {
1931 # Build search filter for hosts
1932 my $search= "(&(objectClass=GOhard)";
1933 foreach (@{$targets}){
1934 $search.= "(macAddress=$_)";
1935 }
1936 $search.= ")";
1938 # If there's any host inside of the search string, procress them
1939 if (!($search =~ /macAddress/)){
1940 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1941 return;
1942 }
1944 # Perform search for Unit Tag
1945 my $mesg = $ldap_handle->search(
1946 base => $ldap_base,
1947 scope => 'sub',
1948 attrs => ['dn', 'FAIstate', 'objectClass'],
1949 filter => "$search"
1950 );
1952 if ($mesg->count) {
1953 my @entries = $mesg->entries;
1954 if (0 == @entries) {
1955 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1956 }
1958 foreach my $entry (@entries) {
1959 # Only modify entry if it is not set to '$state'
1960 if ($entry->get_value("FAIstate") ne "$state"){
1961 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1962 my $result;
1963 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1964 if (exists $tmp{'FAIobject'}){
1965 if ($state eq ''){
1966 $result= $ldap_handle->modify($entry->dn, changes => [
1967 delete => [ FAIstate => [] ] ]);
1968 } else {
1969 $result= $ldap_handle->modify($entry->dn, changes => [
1970 replace => [ FAIstate => $state ] ]);
1971 }
1972 } elsif ($state ne ''){
1973 $result= $ldap_handle->modify($entry->dn, changes => [
1974 add => [ objectClass => 'FAIobject' ],
1975 add => [ FAIstate => $state ] ]);
1976 }
1978 # Errors?
1979 if ($result->code){
1980 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1981 }
1982 } else {
1983 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1984 }
1985 }
1986 } else {
1987 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1988 }
1990 # if no ldap handle defined
1991 } else {
1992 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1993 }
1995 return;
1996 }
1999 sub change_goto_state {
2000 my ($st, $targets, $session_id) = @_;
2001 $session_id = 0 if not defined $session_id;
2003 # Switch on or off?
2004 my $state= $st eq 'active' ? 'active': 'locked';
2006 my $ldap_handle = &get_ldap_handle($session_id);
2007 if( defined($ldap_handle) ) {
2009 # Build search filter for hosts
2010 my $search= "(&(objectClass=GOhard)";
2011 foreach (@{$targets}){
2012 $search.= "(macAddress=$_)";
2013 }
2014 $search.= ")";
2016 # If there's any host inside of the search string, procress them
2017 if (!($search =~ /macAddress/)){
2018 return;
2019 }
2021 # Perform search for Unit Tag
2022 my $mesg = $ldap_handle->search(
2023 base => $ldap_base,
2024 scope => 'sub',
2025 attrs => ['dn', 'gotoMode'],
2026 filter => "$search"
2027 );
2029 if ($mesg->count) {
2030 my @entries = $mesg->entries;
2031 foreach my $entry (@entries) {
2033 # Only modify entry if it is not set to '$state'
2034 if ($entry->get_value("gotoMode") ne $state){
2036 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2037 my $result;
2038 $result= $ldap_handle->modify($entry->dn, changes => [
2039 replace => [ gotoMode => $state ] ]);
2041 # Errors?
2042 if ($result->code){
2043 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2044 }
2046 }
2047 }
2048 } else {
2049 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2050 }
2052 }
2053 }
2056 sub run_recreate_packages_db {
2057 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2058 my $session_id = $session->ID;
2059 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 4);
2060 $kernel->yield('create_fai_release_db');
2061 $kernel->yield('create_fai_server_db');
2062 return;
2063 }
2066 sub run_create_fai_server_db {
2067 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2068 my $session_id = $session->ID;
2069 my $task = POE::Wheel::Run->new(
2070 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2071 StdoutEvent => "session_run_result",
2072 StderrEvent => "session_run_debug",
2073 CloseEvent => "session_run_done",
2074 );
2076 $heap->{task}->{ $task->ID } = $task;
2077 return;
2078 }
2081 sub create_fai_server_db {
2082 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2083 my $result;
2085 if (not defined $session_id) { $session_id = 0; }
2086 my $ldap_handle = &get_ldap_handle();
2087 if(defined($ldap_handle)) {
2088 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2089 my $mesg= $ldap_handle->search(
2090 base => $ldap_base,
2091 scope => 'sub',
2092 attrs => ['FAIrepository', 'gosaUnitTag'],
2093 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2094 );
2095 if($mesg->{'resultCode'} == 0 &&
2096 $mesg->count != 0) {
2097 foreach my $entry (@{$mesg->{entries}}) {
2098 if($entry->exists('FAIrepository')) {
2099 # Add an entry for each Repository configured for server
2100 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2101 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2102 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2103 $result= $fai_server_db->add_dbentry( {
2104 table => $table_name,
2105 primkey => ['server', 'release', 'tag'],
2106 server => $tmp_url,
2107 release => $tmp_release,
2108 sections => $tmp_sections,
2109 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2110 } );
2111 }
2112 }
2113 }
2114 }
2115 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2117 # TODO: Find a way to post the 'create_packages_list_db' event
2118 if(not defined($dont_create_packages_list)) {
2119 &create_packages_list_db(undef, undef, $session_id);
2120 }
2121 }
2123 $ldap_handle->disconnect;
2124 return $result;
2125 }
2128 sub run_create_fai_release_db {
2129 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2130 my $session_id = $session->ID;
2131 my $task = POE::Wheel::Run->new(
2132 Program => sub { &create_fai_release_db($table_name, $session_id) },
2133 StdoutEvent => "session_run_result",
2134 StderrEvent => "session_run_debug",
2135 CloseEvent => "session_run_done",
2136 );
2138 $heap->{task}->{ $task->ID } = $task;
2139 return;
2140 }
2143 sub create_fai_release_db {
2144 my ($table_name, $session_id) = @_;
2145 my $result;
2147 # used for logging
2148 if (not defined $session_id) { $session_id = 0; }
2150 my $ldap_handle = &get_ldap_handle();
2151 if(defined($ldap_handle)) {
2152 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2153 my $mesg= $ldap_handle->search(
2154 base => $ldap_base,
2155 scope => 'sub',
2156 attrs => [],
2157 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2158 );
2159 if($mesg->{'resultCode'} == 0 &&
2160 $mesg->count != 0) {
2161 # Walk through all possible FAI container ou's
2162 my @sql_list;
2163 my $timestamp= &get_time();
2164 foreach my $ou (@{$mesg->{entries}}) {
2165 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2166 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2167 my @tmp_array=get_fai_release_entries($tmp_classes);
2168 if(@tmp_array) {
2169 foreach my $entry (@tmp_array) {
2170 if(defined($entry) && ref($entry) eq 'HASH') {
2171 my $sql=
2172 "INSERT INTO $table_name "
2173 ."(timestamp, release, class, type, state) VALUES ("
2174 .$timestamp.","
2175 ."'".$entry->{'release'}."',"
2176 ."'".$entry->{'class'}."',"
2177 ."'".$entry->{'type'}."',"
2178 ."'".$entry->{'state'}."')";
2179 push @sql_list, $sql;
2180 }
2181 }
2182 }
2183 }
2184 }
2186 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2187 if(@sql_list) {
2188 unshift @sql_list, "VACUUM";
2189 unshift @sql_list, "DELETE FROM $table_name";
2190 $fai_release_db->exec_statementlist(\@sql_list);
2191 }
2192 daemon_log("$session_id DEBUG: Done with inserting",7);
2193 }
2194 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2195 }
2196 $ldap_handle->disconnect;
2197 return $result;
2198 }
2200 sub get_fai_types {
2201 my $tmp_classes = shift || return undef;
2202 my @result;
2204 foreach my $type(keys %{$tmp_classes}) {
2205 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2206 my $entry = {
2207 type => $type,
2208 state => $tmp_classes->{$type}[0],
2209 };
2210 push @result, $entry;
2211 }
2212 }
2214 return @result;
2215 }
2217 sub get_fai_state {
2218 my $result = "";
2219 my $tmp_classes = shift || return $result;
2221 foreach my $type(keys %{$tmp_classes}) {
2222 if(defined($tmp_classes->{$type}[0])) {
2223 $result = $tmp_classes->{$type}[0];
2225 # State is equal for all types in class
2226 last;
2227 }
2228 }
2230 return $result;
2231 }
2233 sub resolve_fai_classes {
2234 my ($fai_base, $ldap_handle, $session_id) = @_;
2235 if (not defined $session_id) { $session_id = 0; }
2236 my $result;
2237 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2238 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2239 my $fai_classes;
2241 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2242 my $mesg= $ldap_handle->search(
2243 base => $fai_base,
2244 scope => 'sub',
2245 attrs => ['cn','objectClass','FAIstate'],
2246 filter => $fai_filter,
2247 );
2248 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2250 if($mesg->{'resultCode'} == 0 &&
2251 $mesg->count != 0) {
2252 foreach my $entry (@{$mesg->{entries}}) {
2253 if($entry->exists('cn')) {
2254 my $tmp_dn= $entry->dn();
2256 # Skip classname and ou dn parts for class
2257 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2259 # Skip classes without releases
2260 if((!defined($tmp_release)) || length($tmp_release)==0) {
2261 next;
2262 }
2264 my $tmp_cn= $entry->get_value('cn');
2265 my $tmp_state= $entry->get_value('FAIstate');
2267 my $tmp_type;
2268 # Get FAI type
2269 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2270 if(grep $_ eq $oclass, @possible_fai_classes) {
2271 $tmp_type= $oclass;
2272 last;
2273 }
2274 }
2276 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2277 # A Subrelease
2278 my @sub_releases = split(/,/, $tmp_release);
2280 # Walk through subreleases and build hash tree
2281 my $hash;
2282 while(my $tmp_sub_release = pop @sub_releases) {
2283 $hash .= "\{'$tmp_sub_release'\}->";
2284 }
2285 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2286 } else {
2287 # A branch, no subrelease
2288 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2289 }
2290 } elsif (!$entry->exists('cn')) {
2291 my $tmp_dn= $entry->dn();
2292 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2294 # Skip classes without releases
2295 if((!defined($tmp_release)) || length($tmp_release)==0) {
2296 next;
2297 }
2299 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2300 # A Subrelease
2301 my @sub_releases= split(/,/, $tmp_release);
2303 # Walk through subreleases and build hash tree
2304 my $hash;
2305 while(my $tmp_sub_release = pop @sub_releases) {
2306 $hash .= "\{'$tmp_sub_release'\}->";
2307 }
2308 # Remove the last two characters
2309 chop($hash);
2310 chop($hash);
2312 eval('$fai_classes->'.$hash.'= {}');
2313 } else {
2314 # A branch, no subrelease
2315 if(!exists($fai_classes->{$tmp_release})) {
2316 $fai_classes->{$tmp_release} = {};
2317 }
2318 }
2319 }
2320 }
2322 # The hash is complete, now we can honor the copy-on-write based missing entries
2323 foreach my $release (keys %$fai_classes) {
2324 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2325 }
2326 }
2327 return $result;
2328 }
2330 sub apply_fai_inheritance {
2331 my $fai_classes = shift || return {};
2332 my $tmp_classes;
2334 # Get the classes from the branch
2335 foreach my $class (keys %{$fai_classes}) {
2336 # Skip subreleases
2337 if($class =~ /^ou=.*$/) {
2338 next;
2339 } else {
2340 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2341 }
2342 }
2344 # Apply to each subrelease
2345 foreach my $subrelease (keys %{$fai_classes}) {
2346 if($subrelease =~ /ou=/) {
2347 foreach my $tmp_class (keys %{$tmp_classes}) {
2348 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2349 $fai_classes->{$subrelease}->{$tmp_class} =
2350 deep_copy($tmp_classes->{$tmp_class});
2351 } else {
2352 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2353 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2354 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2355 deep_copy($tmp_classes->{$tmp_class}->{$type});
2356 }
2357 }
2358 }
2359 }
2360 }
2361 }
2363 # Find subreleases in deeper levels
2364 foreach my $subrelease (keys %{$fai_classes}) {
2365 if($subrelease =~ /ou=/) {
2366 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2367 if($subsubrelease =~ /ou=/) {
2368 apply_fai_inheritance($fai_classes->{$subrelease});
2369 }
2370 }
2371 }
2372 }
2374 return $fai_classes;
2375 }
2377 sub get_fai_release_entries {
2378 my $tmp_classes = shift || return;
2379 my $parent = shift || "";
2380 my @result = shift || ();
2382 foreach my $entry (keys %{$tmp_classes}) {
2383 if(defined($entry)) {
2384 if($entry =~ /^ou=.*$/) {
2385 my $release_name = $entry;
2386 $release_name =~ s/ou=//g;
2387 if(length($parent)>0) {
2388 $release_name = $parent."/".$release_name;
2389 }
2390 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2391 foreach my $bufentry(@bufentries) {
2392 push @result, $bufentry;
2393 }
2394 } else {
2395 my @types = get_fai_types($tmp_classes->{$entry});
2396 foreach my $type (@types) {
2397 push @result,
2398 {
2399 'class' => $entry,
2400 'type' => $type->{'type'},
2401 'release' => $parent,
2402 'state' => $type->{'state'},
2403 };
2404 }
2405 }
2406 }
2407 }
2409 return @result;
2410 }
2412 sub deep_copy {
2413 my $this = shift;
2414 if (not ref $this) {
2415 $this;
2416 } elsif (ref $this eq "ARRAY") {
2417 [map deep_copy($_), @$this];
2418 } elsif (ref $this eq "HASH") {
2419 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2420 } else { die "what type is $_?" }
2421 }
2424 sub session_run_result {
2425 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2426 $kernel->sig(CHLD => "child_reap");
2427 }
2429 sub session_run_debug {
2430 my $result = $_[ARG0];
2431 print STDERR "$result\n";
2432 }
2434 sub session_run_done {
2435 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2436 delete $heap->{task}->{$task_id};
2437 }
2440 sub create_sources_list {
2441 my $session_id = shift;
2442 my $ldap_handle = &main::get_ldap_handle;
2443 my $result="/tmp/gosa_si_tmp_sources_list";
2445 # Remove old file
2446 if(stat($result)) {
2447 unlink($result);
2448 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2449 }
2451 my $fh;
2452 open($fh, ">$result");
2453 if (not defined $fh) {
2454 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2455 return undef;
2456 }
2457 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2458 my $mesg=$ldap_handle->search(
2459 base => $main::ldap_server_dn,
2460 scope => 'base',
2461 attrs => 'FAIrepository',
2462 filter => 'objectClass=FAIrepositoryServer'
2463 );
2464 if($mesg->count) {
2465 foreach my $entry(@{$mesg->{'entries'}}) {
2466 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2467 my ($server, $tag, $release, $sections)= split /\|/, $value;
2468 my $line = "deb $server $release";
2469 $sections =~ s/,/ /g;
2470 $line.= " $sections";
2471 print $fh $line."\n";
2472 }
2473 }
2474 }
2475 } else {
2476 if (defined $main::ldap_server_dn){
2477 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2478 } else {
2479 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2480 }
2481 }
2482 close($fh);
2484 return $result;
2485 }
2488 sub run_create_packages_list_db {
2489 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2490 my $session_id = $session->ID;
2492 my $task = POE::Wheel::Run->new(
2493 Priority => +20,
2494 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2495 StdoutEvent => "session_run_result",
2496 StderrEvent => "session_run_debug",
2497 CloseEvent => "session_run_done",
2498 );
2499 $heap->{task}->{ $task->ID } = $task;
2500 }
2503 sub create_packages_list_db {
2504 my ($ldap_handle, $sources_file, $session_id) = @_;
2506 # it should not be possible to trigger a recreation of packages_list_db
2507 # while packages_list_db is under construction, so set flag packages_list_under_construction
2508 # which is tested befor recreation can be started
2509 if (-r $packages_list_under_construction) {
2510 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2511 return;
2512 } else {
2513 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2514 # set packages_list_under_construction to true
2515 system("touch $packages_list_under_construction");
2516 @packages_list_statements=();
2517 }
2519 if (not defined $session_id) { $session_id = 0; }
2520 if (not defined $ldap_handle) {
2521 $ldap_handle= &get_ldap_handle();
2523 if (not defined $ldap_handle) {
2524 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2525 unlink($packages_list_under_construction);
2526 return;
2527 }
2528 }
2529 if (not defined $sources_file) {
2530 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2531 $sources_file = &create_sources_list($session_id);
2532 }
2534 if (not defined $sources_file) {
2535 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2536 unlink($packages_list_under_construction);
2537 return;
2538 }
2540 my $line;
2542 open(CONFIG, "<$sources_file") or do {
2543 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2544 unlink($packages_list_under_construction);
2545 return;
2546 };
2548 # Read lines
2549 while ($line = <CONFIG>){
2550 # Unify
2551 chop($line);
2552 $line =~ s/^\s+//;
2553 $line =~ s/^\s+/ /;
2555 # Strip comments
2556 $line =~ s/#.*$//g;
2558 # Skip empty lines
2559 if ($line =~ /^\s*$/){
2560 next;
2561 }
2563 # Interpret deb line
2564 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2565 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2566 my $section;
2567 foreach $section (split(' ', $sections)){
2568 &parse_package_info( $baseurl, $dist, $section, $session_id );
2569 }
2570 }
2571 }
2573 close (CONFIG);
2575 find(\&cleanup_and_extract, keys( %repo_dirs ));
2576 &main::strip_packages_list_statements();
2577 unshift @packages_list_statements, "VACUUM";
2578 $packages_list_db->exec_statementlist(\@packages_list_statements);
2579 unlink($packages_list_under_construction);
2580 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2581 return;
2582 }
2584 # This function should do some intensive task to minimize the db-traffic
2585 sub strip_packages_list_statements {
2586 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2587 my @new_statement_list=();
2588 my $hash;
2589 my $insert_hash;
2590 my $update_hash;
2591 my $delete_hash;
2592 my $local_timestamp=get_time();
2594 foreach my $existing_entry (@existing_entries) {
2595 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2596 }
2598 foreach my $statement (@packages_list_statements) {
2599 if($statement =~ /^INSERT/i) {
2600 # Assign the values from the insert statement
2601 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2602 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2603 if(exists($hash->{$distribution}->{$package}->{$version})) {
2604 # If section or description has changed, update the DB
2605 if(
2606 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2607 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2608 ) {
2609 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2610 }
2611 } else {
2612 # Insert a non-existing entry to db
2613 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2614 }
2615 } elsif ($statement =~ /^UPDATE/i) {
2616 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2617 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2618 foreach my $distribution (keys %{$hash}) {
2619 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2620 # update the insertion hash to execute only one query per package (insert instead insert+update)
2621 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2622 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2623 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2624 my $section;
2625 my $description;
2626 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2627 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2628 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2629 }
2630 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2631 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2632 }
2633 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2634 }
2635 }
2636 }
2637 }
2638 }
2640 # TODO: Check for orphaned entries
2642 # unroll the insert_hash
2643 foreach my $distribution (keys %{$insert_hash}) {
2644 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2645 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2646 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2647 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2648 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2649 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2650 ."'$local_timestamp')";
2651 }
2652 }
2653 }
2655 # unroll the update hash
2656 foreach my $distribution (keys %{$update_hash}) {
2657 foreach my $package (keys %{$update_hash->{$distribution}}) {
2658 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2659 my $set = "";
2660 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2661 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2662 }
2663 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2664 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2665 }
2666 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2667 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2668 }
2669 if(defined($set) and length($set) > 0) {
2670 $set .= "timestamp = '$local_timestamp'";
2671 } else {
2672 next;
2673 }
2674 push @new_statement_list,
2675 "UPDATE $main::packages_list_tn SET $set WHERE"
2676 ." distribution = '$distribution'"
2677 ." AND package = '$package'"
2678 ." AND version = '$version'";
2679 }
2680 }
2681 }
2683 @packages_list_statements = @new_statement_list;
2684 }
2687 sub parse_package_info {
2688 my ($baseurl, $dist, $section, $session_id)= @_;
2689 my ($package);
2690 if (not defined $session_id) { $session_id = 0; }
2691 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2692 $repo_dirs{ "${repo_path}/pool" } = 1;
2694 foreach $package ("Packages.gz"){
2695 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2696 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2697 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2698 }
2700 }
2703 sub get_package {
2704 my ($url, $dest, $session_id)= @_;
2705 if (not defined $session_id) { $session_id = 0; }
2707 my $tpath = dirname($dest);
2708 -d "$tpath" || mkpath "$tpath";
2710 # This is ugly, but I've no time to take a look at "how it works in perl"
2711 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2712 system("gunzip -cd '$dest' > '$dest.in'");
2713 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2714 unlink($dest);
2715 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2716 } else {
2717 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2718 }
2719 return 0;
2720 }
2723 sub parse_package {
2724 my ($path, $dist, $srv_path, $session_id)= @_;
2725 if (not defined $session_id) { $session_id = 0;}
2726 my ($package, $version, $section, $description);
2727 my $PACKAGES;
2728 my $timestamp = &get_time();
2730 if(not stat("$path.in")) {
2731 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2732 return;
2733 }
2735 open($PACKAGES, "<$path.in");
2736 if(not defined($PACKAGES)) {
2737 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2738 return;
2739 }
2741 # Read lines
2742 while (<$PACKAGES>){
2743 my $line = $_;
2744 # Unify
2745 chop($line);
2747 # Use empty lines as a trigger
2748 if ($line =~ /^\s*$/){
2749 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2750 push(@packages_list_statements, $sql);
2751 $package = "none";
2752 $version = "none";
2753 $section = "none";
2754 $description = "none";
2755 next;
2756 }
2758 # Trigger for package name
2759 if ($line =~ /^Package:\s/){
2760 ($package)= ($line =~ /^Package: (.*)$/);
2761 next;
2762 }
2764 # Trigger for version
2765 if ($line =~ /^Version:\s/){
2766 ($version)= ($line =~ /^Version: (.*)$/);
2767 next;
2768 }
2770 # Trigger for description
2771 if ($line =~ /^Description:\s/){
2772 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2773 next;
2774 }
2776 # Trigger for section
2777 if ($line =~ /^Section:\s/){
2778 ($section)= ($line =~ /^Section: (.*)$/);
2779 next;
2780 }
2782 # Trigger for filename
2783 if ($line =~ /^Filename:\s/){
2784 my ($filename) = ($line =~ /^Filename: (.*)$/);
2785 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2786 next;
2787 }
2788 }
2790 close( $PACKAGES );
2791 unlink( "$path.in" );
2792 }
2795 sub store_fileinfo {
2796 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2798 my %fileinfo = (
2799 'package' => $package,
2800 'dist' => $dist,
2801 'version' => $vers,
2802 );
2804 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2805 }
2808 sub cleanup_and_extract {
2809 my $fileinfo = $repo_files{ $File::Find::name };
2811 if( defined $fileinfo ) {
2813 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2814 my $sql;
2815 my $package = $fileinfo->{ 'package' };
2816 my $newver = $fileinfo->{ 'version' };
2818 mkpath($dir);
2819 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2821 if( -f "$dir/DEBIAN/templates" ) {
2823 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2825 my $tmpl= "";
2826 {
2827 local $/=undef;
2828 open FILE, "$dir/DEBIAN/templates";
2829 $tmpl = &encode_base64(<FILE>);
2830 close FILE;
2831 }
2832 rmtree("$dir/DEBIAN/templates");
2834 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2835 push @packages_list_statements, $sql;
2836 }
2837 }
2839 return;
2840 }
2843 sub register_at_foreign_servers {
2844 my ($kernel) = $_[KERNEL];
2846 # hole alle bekannten server aus known_server_db
2847 my $server_sql = "SELECT * FROM $known_server_tn";
2848 my $server_res = $known_server_db->exec_statement($server_sql);
2850 # no entries in known_server_db
2851 if (not ref(@$server_res[0]) eq "ARRAY") {
2852 # TODO
2853 }
2855 # detect already connected clients
2856 my $client_sql = "SELECT * FROM $known_clients_tn";
2857 my $client_res = $known_clients_db->exec_statement($client_sql);
2859 # send my server details to all other gosa-si-server within the network
2860 foreach my $hit (@$server_res) {
2861 my $hostname = @$hit[0];
2862 my $hostkey = &create_passwd;
2864 # add already connected clients to registration message
2865 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2866 &add_content2xml_hash($myhash, 'key', $hostkey);
2867 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2869 # build registration message and send it
2870 my $foreign_server_msg = &create_xml_string($myhash);
2871 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2872 }
2874 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2875 return;
2876 }
2879 #==== MAIN = main ==============================================================
2880 # parse commandline options
2881 Getopt::Long::Configure( "bundling" );
2882 GetOptions("h|help" => \&usage,
2883 "c|config=s" => \$cfg_file,
2884 "f|foreground" => \$foreground,
2885 "v|verbose+" => \$verbose,
2886 "no-arp+" => \$no_arp,
2887 );
2889 # read and set config parameters
2890 &check_cmdline_param ;
2891 &read_configfile;
2892 &check_pid;
2894 $SIG{CHLD} = 'IGNORE';
2896 # forward error messages to logfile
2897 if( ! $foreground ) {
2898 open( STDIN, '+>/dev/null' );
2899 open( STDOUT, '+>&STDIN' );
2900 open( STDERR, '+>&STDIN' );
2901 }
2903 # Just fork, if we are not in foreground mode
2904 if( ! $foreground ) {
2905 chdir '/' or die "Can't chdir to /: $!";
2906 $pid = fork;
2907 setsid or die "Can't start a new session: $!";
2908 umask 0;
2909 } else {
2910 $pid = $$;
2911 }
2913 # Do something useful - put our PID into the pid_file
2914 if( 0 != $pid ) {
2915 open( LOCK_FILE, ">$pid_file" );
2916 print LOCK_FILE "$pid\n";
2917 close( LOCK_FILE );
2918 if( !$foreground ) {
2919 exit( 0 )
2920 };
2921 }
2923 # parse head url and revision from svn
2924 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2925 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2926 $server_headURL = defined $1 ? $1 : 'unknown' ;
2927 $server_revision = defined $2 ? $2 : 'unknown' ;
2928 if ($server_headURL =~ /\/tag\// ||
2929 $server_headURL =~ /\/branches\// ) {
2930 $server_status = "stable";
2931 } else {
2932 $server_status = "developmental" ;
2933 }
2936 daemon_log(" ", 1);
2937 daemon_log("$0 started!", 1);
2938 daemon_log("status: $server_status", 1);
2939 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2941 # connect to incoming_db
2942 unlink($incoming_file_name);
2943 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2944 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2946 # connect to gosa-si job queue
2947 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2948 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2950 # connect to known_clients_db
2951 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2952 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2954 # connect to foreign_clients_db
2955 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2956 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2958 # connect to known_server_db
2959 unlink($known_server_file_name);
2960 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2961 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2963 # connect to login_usr_db
2964 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2965 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2967 # connect to fai_server_db and fai_release_db
2968 unlink($fai_server_file_name);
2969 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2970 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2972 unlink($fai_release_file_name);
2973 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2974 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2976 # connect to packages_list_db
2977 #unlink($packages_list_file_name);
2978 unlink($packages_list_under_construction);
2979 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2980 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2982 # connect to messaging_db
2983 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2984 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2987 # create xml object used for en/decrypting
2988 $xml = new XML::Simple();
2991 # foreign servers
2992 my @foreign_server_list;
2994 # add foreign server from cfg file
2995 if ($foreign_server_string ne "") {
2996 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2997 foreach my $foreign_server (@cfg_foreign_server_list) {
2998 push(@foreign_server_list, $foreign_server);
2999 }
3000 }
3002 # add foreign server from dns
3003 my @tmp_servers;
3004 if ( !$server_domain) {
3005 # Try our DNS Searchlist
3006 for my $domain(get_dns_domains()) {
3007 chomp($domain);
3008 my @tmp_domains= &get_server_addresses($domain);
3009 if(@tmp_domains) {
3010 for my $tmp_server(@tmp_domains) {
3011 push @tmp_servers, $tmp_server;
3012 }
3013 }
3014 }
3015 if(@tmp_servers && length(@tmp_servers)==0) {
3016 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3017 }
3018 } else {
3019 @tmp_servers = &get_server_addresses($server_domain);
3020 if( 0 == @tmp_servers ) {
3021 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3022 }
3023 }
3024 foreach my $server (@tmp_servers) {
3025 unshift(@foreign_server_list, $server);
3026 }
3027 # eliminate duplicate entries
3028 @foreign_server_list = &del_doubles(@foreign_server_list);
3029 my $all_foreign_server = join(", ", @foreign_server_list);
3030 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3032 # add all found foreign servers to known_server
3033 my $act_timestamp = &get_time();
3034 foreach my $foreign_server (@foreign_server_list) {
3036 # do not add myself to known_server_db
3037 if (&is_local($foreign_server)) { next; }
3038 ######################################
3040 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3041 primkey=>['hostname'],
3042 hostname=>$foreign_server,
3043 status=>'not_jet_registered',
3044 hostkey=>"none",
3045 timestamp=>$act_timestamp,
3046 } );
3047 }
3050 POE::Component::Server::TCP->new(
3051 Alias => "TCP_SERVER",
3052 Port => $server_port,
3053 ClientInput => sub {
3054 my ($kernel, $input) = @_[KERNEL, ARG0];
3055 push(@tasks, $input);
3056 push(@msgs_to_decrypt, $input);
3057 $kernel->yield("msg_to_decrypt");
3058 },
3059 InlineStates => {
3060 msg_to_decrypt => \&msg_to_decrypt,
3061 next_task => \&next_task,
3062 task_result => \&handle_task_result,
3063 task_done => \&handle_task_done,
3064 task_debug => \&handle_task_debug,
3065 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3066 }
3067 );
3069 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3071 # create session for repeatedly checking the job queue for jobs
3072 POE::Session->create(
3073 inline_states => {
3074 _start => \&session_start,
3075 register_at_foreign_servers => \®ister_at_foreign_servers,
3076 sig_handler => \&sig_handler,
3077 next_task => \&next_task,
3078 task_result => \&handle_task_result,
3079 task_done => \&handle_task_done,
3080 task_debug => \&handle_task_debug,
3081 watch_for_next_tasks => \&watch_for_next_tasks,
3082 watch_for_new_messages => \&watch_for_new_messages,
3083 watch_for_delivery_messages => \&watch_for_delivery_messages,
3084 watch_for_done_messages => \&watch_for_done_messages,
3085 watch_for_new_jobs => \&watch_for_new_jobs,
3086 watch_for_modified_jobs => \&watch_for_modified_jobs,
3087 watch_for_done_jobs => \&watch_for_done_jobs,
3088 watch_for_old_known_clients => \&watch_for_old_known_clients,
3089 create_packages_list_db => \&run_create_packages_list_db,
3090 create_fai_server_db => \&run_create_fai_server_db,
3091 create_fai_release_db => \&run_create_fai_release_db,
3092 recreate_packages_db => \&run_recreate_packages_db,
3093 session_run_result => \&session_run_result,
3094 session_run_debug => \&session_run_debug,
3095 session_run_done => \&session_run_done,
3096 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3097 }
3098 );
3101 # import all modules
3102 &import_modules;
3104 # TODO
3105 # check wether all modules are gosa-si valid passwd check
3109 POE::Kernel->run();
3110 exit;