b6d603042a055468665e565c493edd3b3c7f547e
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'";
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 # determine all other si-server a foreign_job_updates message should be send
1513 my $sql_statement= "SELECT * FROM $known_server_tn";
1514 my $res = $known_server_db->select_dbentry( $sql_statement );
1515 while( my ($hit_num, $hit) = each %$res ) {
1516 my $act_update_msg = $update_msg;
1517 my $act_target_address = $hit->{hostname};
1518 my $act_target_key = $hit->{hostkey};
1519 my ($act_target_ip, $act_target_port) = split(/:/, $act_target_address);
1520 my $act_source_address = &get_local_ip_for_remote_ip($act_target_ip).":$act_target_port";
1522 $act_update_msg =~ s/<target>KNOWN_SERVER<\/target>/<target>$act_target_address<\/target>/g;
1523 $act_update_msg =~ s/<source>MY_LOCAL_ADDRESS<\/source>/<source>$act_source_address<\/source>/g;
1524 &send_msg_to_target($act_update_msg, $act_target_address, $act_target_key, "foreign_job_updates" , "J");
1525 }
1527 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1528 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1529 $res = $job_db->update_dbentry($sql_statement);
1530 }
1532 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1533 }
1536 sub watch_for_new_jobs {
1537 if($watch_for_new_jobs_in_progress == 0) {
1538 $watch_for_new_jobs_in_progress = 1;
1539 my ($kernel,$heap) = @_[KERNEL, HEAP];
1541 # check gosa job quaeue for jobs with executable timestamp
1542 my $timestamp = &get_time();
1543 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1544 my $res = $job_db->exec_statement( $sql_statement );
1546 # Merge all new jobs that would do the same actions
1547 my @drops;
1548 my $hits;
1549 foreach my $hit (reverse @{$res} ) {
1550 my $macaddress= lc @{$hit}[8];
1551 my $headertag= @{$hit}[5];
1552 if(
1553 defined($hits->{$macaddress}) &&
1554 defined($hits->{$macaddress}->{$headertag}) &&
1555 defined($hits->{$macaddress}->{$headertag}[0])
1556 ) {
1557 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1558 }
1559 $hits->{$macaddress}->{$headertag}= $hit;
1560 }
1562 # Delete new jobs with a matching job in state 'processing'
1563 foreach my $macaddress (keys %{$hits}) {
1564 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1565 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1566 if(defined($jobdb_id)) {
1567 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1568 my $res = $job_db->exec_statement( $sql_statement );
1569 foreach my $hit (@{$res}) {
1570 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1571 }
1572 } else {
1573 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1574 }
1575 }
1576 }
1578 # Commit deletion
1579 $job_db->exec_statementlist(\@drops);
1581 # Look for new jobs that could be executed
1582 foreach my $macaddress (keys %{$hits}) {
1584 # Look if there is an executing job
1585 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1586 my $res = $job_db->exec_statement( $sql_statement );
1588 # Skip new jobs for host if there is a processing job
1589 if(defined($res) and defined @{$res}[0]) {
1590 next;
1591 }
1593 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1594 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1595 if(defined($jobdb_id)) {
1596 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1598 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1599 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1600 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1602 # expect macaddress is unique!!!!!!
1603 my $target = $res_hash->{1}->{hostname};
1605 # change header
1606 $job_msg =~ s/<header>job_/<header>gosa_/;
1608 # add sqlite_id
1609 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1611 $job_msg =~ /<header>(\S+)<\/header>/;
1612 my $header = $1 ;
1613 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1615 # update status in job queue to 'processing'
1616 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1617 my $res = $job_db->update_dbentry($sql_statement);
1618 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1620 # We don't want parallel processing
1621 last;
1622 }
1623 }
1624 }
1626 $watch_for_new_jobs_in_progress = 0;
1627 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1628 }
1629 }
1633 sub watch_for_new_messages {
1634 my ($kernel,$heap) = @_[KERNEL, HEAP];
1635 my @coll_user_msg; # collection list of outgoing messages
1637 # check messaging_db for new incoming messages with executable timestamp
1638 my $timestamp = &get_time();
1639 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1640 my $res = $messaging_db->exec_statement( $sql_statement );
1641 foreach my $hit (@{$res}) {
1643 # create outgoing messages
1644 my $message_to = @{$hit}[3];
1645 # translate message_to to plain login name
1646 my @message_to_l = split(/,/, $message_to);
1647 my %receiver_h;
1648 foreach my $receiver (@message_to_l) {
1649 if ($receiver =~ /^u_([\s\S]*)$/) {
1650 $receiver_h{$1} = 0;
1651 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1652 my $group_name = $1;
1653 # fetch all group members from ldap and add them to receiver hash
1654 my $ldap_handle = &get_ldap_handle();
1655 if (defined $ldap_handle) {
1656 my $mesg = $ldap_handle->search(
1657 base => $ldap_base,
1658 scope => 'sub',
1659 attrs => ['memberUid'],
1660 filter => "cn=$group_name",
1661 );
1662 if ($mesg->count) {
1663 my @entries = $mesg->entries;
1664 foreach my $entry (@entries) {
1665 my @receivers= $entry->get_value("memberUid");
1666 foreach my $receiver (@receivers) {
1667 $receiver_h{$1} = 0;
1668 }
1669 }
1670 }
1671 # translating errors ?
1672 if ($mesg->code) {
1673 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1674 }
1675 # ldap handle error ?
1676 } else {
1677 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1678 }
1679 } else {
1680 my $sbjct = &encode_base64(@{$hit}[1]);
1681 my $msg = &encode_base64(@{$hit}[7]);
1682 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1683 }
1684 }
1685 my @receiver_l = keys(%receiver_h);
1687 my $message_id = @{$hit}[0];
1689 #add each outgoing msg to messaging_db
1690 my $receiver;
1691 foreach $receiver (@receiver_l) {
1692 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1693 "VALUES ('".
1694 $message_id."', '". # id
1695 @{$hit}[1]."', '". # subject
1696 @{$hit}[2]."', '". # message_from
1697 $receiver."', '". # message_to
1698 "none"."', '". # flag
1699 "out"."', '". # direction
1700 @{$hit}[6]."', '". # delivery_time
1701 @{$hit}[7]."', '". # message
1702 $timestamp."'". # timestamp
1703 ")";
1704 &daemon_log("M DEBUG: $sql_statement", 1);
1705 my $res = $messaging_db->exec_statement($sql_statement);
1706 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1707 }
1709 # set incoming message to flag d=deliverd
1710 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1711 &daemon_log("M DEBUG: $sql_statement", 7);
1712 $res = $messaging_db->update_dbentry($sql_statement);
1713 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1714 }
1716 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1717 return;
1718 }
1720 sub watch_for_delivery_messages {
1721 my ($kernel, $heap) = @_[KERNEL, HEAP];
1723 # select outgoing messages
1724 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1725 #&daemon_log("0 DEBUG: $sql", 7);
1726 my $res = $messaging_db->exec_statement( $sql_statement );
1728 # build out msg for each usr
1729 foreach my $hit (@{$res}) {
1730 my $receiver = @{$hit}[3];
1731 my $msg_id = @{$hit}[0];
1732 my $subject = @{$hit}[1];
1733 my $message = @{$hit}[7];
1735 # resolve usr -> host where usr is logged in
1736 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1737 #&daemon_log("0 DEBUG: $sql", 7);
1738 my $res = $login_users_db->exec_statement($sql);
1740 # reciver is logged in nowhere
1741 if (not ref(@$res[0]) eq "ARRAY") { next; }
1743 my $send_succeed = 0;
1744 foreach my $hit (@$res) {
1745 my $receiver_host = @$hit[0];
1746 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1748 # fetch key to encrypt msg propperly for usr/host
1749 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1750 &daemon_log("0 DEBUG: $sql", 7);
1751 my $res = $known_clients_db->exec_statement($sql);
1753 # host is already down
1754 if (not ref(@$res[0]) eq "ARRAY") { next; }
1756 # host is on
1757 my $receiver_key = @{@{$res}[0]}[2];
1758 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1759 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1760 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1761 if ($error == 0 ) {
1762 $send_succeed++ ;
1763 }
1764 }
1766 if ($send_succeed) {
1767 # set outgoing msg at db to deliverd
1768 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1769 &daemon_log("0 DEBUG: $sql", 7);
1770 my $res = $messaging_db->exec_statement($sql);
1771 }
1772 }
1774 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1775 return;
1776 }
1779 sub watch_for_done_messages {
1780 my ($kernel,$heap) = @_[KERNEL, HEAP];
1782 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1783 #&daemon_log("0 DEBUG: $sql", 7);
1784 my $res = $messaging_db->exec_statement($sql);
1786 foreach my $hit (@{$res}) {
1787 my $msg_id = @{$hit}[0];
1789 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1790 #&daemon_log("0 DEBUG: $sql", 7);
1791 my $res = $messaging_db->exec_statement($sql);
1793 # not all usr msgs have been seen till now
1794 if ( ref(@$res[0]) eq "ARRAY") { next; }
1796 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1797 #&daemon_log("0 DEBUG: $sql", 7);
1798 $res = $messaging_db->exec_statement($sql);
1800 }
1802 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1803 return;
1804 }
1807 sub watch_for_old_known_clients {
1808 my ($kernel,$heap) = @_[KERNEL, HEAP];
1810 my $sql_statement = "SELECT * FROM $known_clients_tn";
1811 my $res = $known_clients_db->select_dbentry( $sql_statement );
1813 my $act_time = int(&get_time());
1815 while ( my ($hit_num, $hit) = each %$res) {
1816 my $expired_timestamp = int($hit->{'timestamp'});
1817 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1818 my $dt = DateTime->new( year => $1,
1819 month => $2,
1820 day => $3,
1821 hour => $4,
1822 minute => $5,
1823 second => $6,
1824 );
1826 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1827 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1828 if ($act_time > $expired_timestamp) {
1829 my $hostname = $hit->{'hostname'};
1830 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1831 my $del_res = $known_clients_db->exec_statement($del_sql);
1833 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1834 }
1836 }
1838 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1839 }
1842 sub watch_for_next_tasks {
1843 my ($kernel,$heap) = @_[KERNEL, HEAP];
1845 my $sql = "SELECT * FROM $incoming_tn";
1846 my $res = $incoming_db->select_dbentry($sql);
1848 while ( my ($hit_num, $hit) = each %$res) {
1849 my $headertag = $hit->{'headertag'};
1850 if ($headertag =~ /^answer_(\d+)/) {
1851 # do not start processing, this message is for a still running POE::Wheel
1852 next;
1853 }
1854 my $message_id = $hit->{'id'};
1855 $kernel->yield('next_task', $hit);
1857 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1858 my $res = $incoming_db->exec_statement($sql);
1859 }
1861 $kernel->delay_set('watch_for_next_tasks', 0.1);
1862 }
1865 sub get_ldap_handle {
1866 my ($session_id) = @_;
1867 my $heap;
1868 my $ldap_handle;
1870 if (not defined $session_id ) { $session_id = 0 };
1871 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1873 if ($session_id == 0) {
1874 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1875 $ldap_handle = Net::LDAP->new( $ldap_uri );
1876 $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!");
1878 } else {
1879 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1880 if( defined $session_reference ) {
1881 $heap = $session_reference->get_heap();
1882 }
1884 if (not defined $heap) {
1885 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1886 return;
1887 }
1889 # TODO: This "if" is nonsense, because it doesn't prove that the
1890 # used handle is still valid - or if we've to reconnect...
1891 #if (not exists $heap->{ldap_handle}) {
1892 $ldap_handle = Net::LDAP->new( $ldap_uri );
1893 $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!");
1894 $heap->{ldap_handle} = $ldap_handle;
1895 #}
1896 }
1897 return $ldap_handle;
1898 }
1901 sub change_fai_state {
1902 my ($st, $targets, $session_id) = @_;
1903 $session_id = 0 if not defined $session_id;
1904 # Set FAI state to localboot
1905 my %mapActions= (
1906 reboot => '',
1907 update => 'softupdate',
1908 localboot => 'localboot',
1909 reinstall => 'install',
1910 rescan => '',
1911 wake => '',
1912 memcheck => 'memcheck',
1913 sysinfo => 'sysinfo',
1914 install => 'install',
1915 );
1917 # Return if this is unknown
1918 if (!exists $mapActions{ $st }){
1919 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1920 return;
1921 }
1923 my $state= $mapActions{ $st };
1925 my $ldap_handle = &get_ldap_handle($session_id);
1926 if( defined($ldap_handle) ) {
1928 # Build search filter for hosts
1929 my $search= "(&(objectClass=GOhard)";
1930 foreach (@{$targets}){
1931 $search.= "(macAddress=$_)";
1932 }
1933 $search.= ")";
1935 # If there's any host inside of the search string, procress them
1936 if (!($search =~ /macAddress/)){
1937 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1938 return;
1939 }
1941 # Perform search for Unit Tag
1942 my $mesg = $ldap_handle->search(
1943 base => $ldap_base,
1944 scope => 'sub',
1945 attrs => ['dn', 'FAIstate', 'objectClass'],
1946 filter => "$search"
1947 );
1949 if ($mesg->count) {
1950 my @entries = $mesg->entries;
1951 if (0 == @entries) {
1952 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1953 }
1955 foreach my $entry (@entries) {
1956 # Only modify entry if it is not set to '$state'
1957 if ($entry->get_value("FAIstate") ne "$state"){
1958 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1959 my $result;
1960 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1961 if (exists $tmp{'FAIobject'}){
1962 if ($state eq ''){
1963 $result= $ldap_handle->modify($entry->dn, changes => [
1964 delete => [ FAIstate => [] ] ]);
1965 } else {
1966 $result= $ldap_handle->modify($entry->dn, changes => [
1967 replace => [ FAIstate => $state ] ]);
1968 }
1969 } elsif ($state ne ''){
1970 $result= $ldap_handle->modify($entry->dn, changes => [
1971 add => [ objectClass => 'FAIobject' ],
1972 add => [ FAIstate => $state ] ]);
1973 }
1975 # Errors?
1976 if ($result->code){
1977 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1978 }
1979 } else {
1980 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1981 }
1982 }
1983 } else {
1984 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1985 }
1987 # if no ldap handle defined
1988 } else {
1989 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1990 }
1992 return;
1993 }
1996 sub change_goto_state {
1997 my ($st, $targets, $session_id) = @_;
1998 $session_id = 0 if not defined $session_id;
2000 # Switch on or off?
2001 my $state= $st eq 'active' ? 'active': 'locked';
2003 my $ldap_handle = &get_ldap_handle($session_id);
2004 if( defined($ldap_handle) ) {
2006 # Build search filter for hosts
2007 my $search= "(&(objectClass=GOhard)";
2008 foreach (@{$targets}){
2009 $search.= "(macAddress=$_)";
2010 }
2011 $search.= ")";
2013 # If there's any host inside of the search string, procress them
2014 if (!($search =~ /macAddress/)){
2015 return;
2016 }
2018 # Perform search for Unit Tag
2019 my $mesg = $ldap_handle->search(
2020 base => $ldap_base,
2021 scope => 'sub',
2022 attrs => ['dn', 'gotoMode'],
2023 filter => "$search"
2024 );
2026 if ($mesg->count) {
2027 my @entries = $mesg->entries;
2028 foreach my $entry (@entries) {
2030 # Only modify entry if it is not set to '$state'
2031 if ($entry->get_value("gotoMode") ne $state){
2033 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2034 my $result;
2035 $result= $ldap_handle->modify($entry->dn, changes => [
2036 replace => [ gotoMode => $state ] ]);
2038 # Errors?
2039 if ($result->code){
2040 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2041 }
2043 }
2044 }
2045 } else {
2046 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2047 }
2049 }
2050 }
2053 sub run_recreate_packages_db {
2054 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2055 my $session_id = $session->ID;
2056 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 4);
2057 $kernel->yield('create_fai_release_db');
2058 $kernel->yield('create_fai_server_db');
2059 return;
2060 }
2063 sub run_create_fai_server_db {
2064 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2065 my $session_id = $session->ID;
2066 my $task = POE::Wheel::Run->new(
2067 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2068 StdoutEvent => "session_run_result",
2069 StderrEvent => "session_run_debug",
2070 CloseEvent => "session_run_done",
2071 );
2073 $heap->{task}->{ $task->ID } = $task;
2074 return;
2075 }
2078 sub create_fai_server_db {
2079 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2080 my $result;
2082 if (not defined $session_id) { $session_id = 0; }
2083 my $ldap_handle = &get_ldap_handle();
2084 if(defined($ldap_handle)) {
2085 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2086 my $mesg= $ldap_handle->search(
2087 base => $ldap_base,
2088 scope => 'sub',
2089 attrs => ['FAIrepository', 'gosaUnitTag'],
2090 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2091 );
2092 if($mesg->{'resultCode'} == 0 &&
2093 $mesg->count != 0) {
2094 foreach my $entry (@{$mesg->{entries}}) {
2095 if($entry->exists('FAIrepository')) {
2096 # Add an entry for each Repository configured for server
2097 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2098 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2099 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2100 $result= $fai_server_db->add_dbentry( {
2101 table => $table_name,
2102 primkey => ['server', 'release', 'tag'],
2103 server => $tmp_url,
2104 release => $tmp_release,
2105 sections => $tmp_sections,
2106 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2107 } );
2108 }
2109 }
2110 }
2111 }
2112 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2114 # TODO: Find a way to post the 'create_packages_list_db' event
2115 if(not defined($dont_create_packages_list)) {
2116 &create_packages_list_db(undef, undef, $session_id);
2117 }
2118 }
2120 $ldap_handle->disconnect;
2121 return $result;
2122 }
2125 sub run_create_fai_release_db {
2126 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2127 my $session_id = $session->ID;
2128 my $task = POE::Wheel::Run->new(
2129 Program => sub { &create_fai_release_db($table_name, $session_id) },
2130 StdoutEvent => "session_run_result",
2131 StderrEvent => "session_run_debug",
2132 CloseEvent => "session_run_done",
2133 );
2135 $heap->{task}->{ $task->ID } = $task;
2136 return;
2137 }
2140 sub create_fai_release_db {
2141 my ($table_name, $session_id) = @_;
2142 my $result;
2144 # used for logging
2145 if (not defined $session_id) { $session_id = 0; }
2147 my $ldap_handle = &get_ldap_handle();
2148 if(defined($ldap_handle)) {
2149 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2150 my $mesg= $ldap_handle->search(
2151 base => $ldap_base,
2152 scope => 'sub',
2153 attrs => [],
2154 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2155 );
2156 if($mesg->{'resultCode'} == 0 &&
2157 $mesg->count != 0) {
2158 # Walk through all possible FAI container ou's
2159 my @sql_list;
2160 my $timestamp= &get_time();
2161 foreach my $ou (@{$mesg->{entries}}) {
2162 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2163 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2164 my @tmp_array=get_fai_release_entries($tmp_classes);
2165 if(@tmp_array) {
2166 foreach my $entry (@tmp_array) {
2167 if(defined($entry) && ref($entry) eq 'HASH') {
2168 my $sql=
2169 "INSERT INTO $table_name "
2170 ."(timestamp, release, class, type, state) VALUES ("
2171 .$timestamp.","
2172 ."'".$entry->{'release'}."',"
2173 ."'".$entry->{'class'}."',"
2174 ."'".$entry->{'type'}."',"
2175 ."'".$entry->{'state'}."')";
2176 push @sql_list, $sql;
2177 }
2178 }
2179 }
2180 }
2181 }
2183 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2184 if(@sql_list) {
2185 unshift @sql_list, "VACUUM";
2186 unshift @sql_list, "DELETE FROM $table_name";
2187 $fai_release_db->exec_statementlist(\@sql_list);
2188 }
2189 daemon_log("$session_id DEBUG: Done with inserting",7);
2190 }
2191 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2192 }
2193 $ldap_handle->disconnect;
2194 return $result;
2195 }
2197 sub get_fai_types {
2198 my $tmp_classes = shift || return undef;
2199 my @result;
2201 foreach my $type(keys %{$tmp_classes}) {
2202 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2203 my $entry = {
2204 type => $type,
2205 state => $tmp_classes->{$type}[0],
2206 };
2207 push @result, $entry;
2208 }
2209 }
2211 return @result;
2212 }
2214 sub get_fai_state {
2215 my $result = "";
2216 my $tmp_classes = shift || return $result;
2218 foreach my $type(keys %{$tmp_classes}) {
2219 if(defined($tmp_classes->{$type}[0])) {
2220 $result = $tmp_classes->{$type}[0];
2222 # State is equal for all types in class
2223 last;
2224 }
2225 }
2227 return $result;
2228 }
2230 sub resolve_fai_classes {
2231 my ($fai_base, $ldap_handle, $session_id) = @_;
2232 if (not defined $session_id) { $session_id = 0; }
2233 my $result;
2234 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2235 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2236 my $fai_classes;
2238 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2239 my $mesg= $ldap_handle->search(
2240 base => $fai_base,
2241 scope => 'sub',
2242 attrs => ['cn','objectClass','FAIstate'],
2243 filter => $fai_filter,
2244 );
2245 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2247 if($mesg->{'resultCode'} == 0 &&
2248 $mesg->count != 0) {
2249 foreach my $entry (@{$mesg->{entries}}) {
2250 if($entry->exists('cn')) {
2251 my $tmp_dn= $entry->dn();
2253 # Skip classname and ou dn parts for class
2254 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2256 # Skip classes without releases
2257 if((!defined($tmp_release)) || length($tmp_release)==0) {
2258 next;
2259 }
2261 my $tmp_cn= $entry->get_value('cn');
2262 my $tmp_state= $entry->get_value('FAIstate');
2264 my $tmp_type;
2265 # Get FAI type
2266 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2267 if(grep $_ eq $oclass, @possible_fai_classes) {
2268 $tmp_type= $oclass;
2269 last;
2270 }
2271 }
2273 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2274 # A Subrelease
2275 my @sub_releases = split(/,/, $tmp_release);
2277 # Walk through subreleases and build hash tree
2278 my $hash;
2279 while(my $tmp_sub_release = pop @sub_releases) {
2280 $hash .= "\{'$tmp_sub_release'\}->";
2281 }
2282 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2283 } else {
2284 # A branch, no subrelease
2285 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2286 }
2287 } elsif (!$entry->exists('cn')) {
2288 my $tmp_dn= $entry->dn();
2289 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2291 # Skip classes without releases
2292 if((!defined($tmp_release)) || length($tmp_release)==0) {
2293 next;
2294 }
2296 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2297 # A Subrelease
2298 my @sub_releases= split(/,/, $tmp_release);
2300 # Walk through subreleases and build hash tree
2301 my $hash;
2302 while(my $tmp_sub_release = pop @sub_releases) {
2303 $hash .= "\{'$tmp_sub_release'\}->";
2304 }
2305 # Remove the last two characters
2306 chop($hash);
2307 chop($hash);
2309 eval('$fai_classes->'.$hash.'= {}');
2310 } else {
2311 # A branch, no subrelease
2312 if(!exists($fai_classes->{$tmp_release})) {
2313 $fai_classes->{$tmp_release} = {};
2314 }
2315 }
2316 }
2317 }
2319 # The hash is complete, now we can honor the copy-on-write based missing entries
2320 foreach my $release (keys %$fai_classes) {
2321 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2322 }
2323 }
2324 return $result;
2325 }
2327 sub apply_fai_inheritance {
2328 my $fai_classes = shift || return {};
2329 my $tmp_classes;
2331 # Get the classes from the branch
2332 foreach my $class (keys %{$fai_classes}) {
2333 # Skip subreleases
2334 if($class =~ /^ou=.*$/) {
2335 next;
2336 } else {
2337 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2338 }
2339 }
2341 # Apply to each subrelease
2342 foreach my $subrelease (keys %{$fai_classes}) {
2343 if($subrelease =~ /ou=/) {
2344 foreach my $tmp_class (keys %{$tmp_classes}) {
2345 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2346 $fai_classes->{$subrelease}->{$tmp_class} =
2347 deep_copy($tmp_classes->{$tmp_class});
2348 } else {
2349 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2350 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2351 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2352 deep_copy($tmp_classes->{$tmp_class}->{$type});
2353 }
2354 }
2355 }
2356 }
2357 }
2358 }
2360 # Find subreleases in deeper levels
2361 foreach my $subrelease (keys %{$fai_classes}) {
2362 if($subrelease =~ /ou=/) {
2363 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2364 if($subsubrelease =~ /ou=/) {
2365 apply_fai_inheritance($fai_classes->{$subrelease});
2366 }
2367 }
2368 }
2369 }
2371 return $fai_classes;
2372 }
2374 sub get_fai_release_entries {
2375 my $tmp_classes = shift || return;
2376 my $parent = shift || "";
2377 my @result = shift || ();
2379 foreach my $entry (keys %{$tmp_classes}) {
2380 if(defined($entry)) {
2381 if($entry =~ /^ou=.*$/) {
2382 my $release_name = $entry;
2383 $release_name =~ s/ou=//g;
2384 if(length($parent)>0) {
2385 $release_name = $parent."/".$release_name;
2386 }
2387 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2388 foreach my $bufentry(@bufentries) {
2389 push @result, $bufentry;
2390 }
2391 } else {
2392 my @types = get_fai_types($tmp_classes->{$entry});
2393 foreach my $type (@types) {
2394 push @result,
2395 {
2396 'class' => $entry,
2397 'type' => $type->{'type'},
2398 'release' => $parent,
2399 'state' => $type->{'state'},
2400 };
2401 }
2402 }
2403 }
2404 }
2406 return @result;
2407 }
2409 sub deep_copy {
2410 my $this = shift;
2411 if (not ref $this) {
2412 $this;
2413 } elsif (ref $this eq "ARRAY") {
2414 [map deep_copy($_), @$this];
2415 } elsif (ref $this eq "HASH") {
2416 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2417 } else { die "what type is $_?" }
2418 }
2421 sub session_run_result {
2422 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2423 $kernel->sig(CHLD => "child_reap");
2424 }
2426 sub session_run_debug {
2427 my $result = $_[ARG0];
2428 print STDERR "$result\n";
2429 }
2431 sub session_run_done {
2432 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2433 delete $heap->{task}->{$task_id};
2434 }
2437 sub create_sources_list {
2438 my $session_id = shift;
2439 my $ldap_handle = &main::get_ldap_handle;
2440 my $result="/tmp/gosa_si_tmp_sources_list";
2442 # Remove old file
2443 if(stat($result)) {
2444 unlink($result);
2445 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2446 }
2448 my $fh;
2449 open($fh, ">$result");
2450 if (not defined $fh) {
2451 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2452 return undef;
2453 }
2454 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2455 my $mesg=$ldap_handle->search(
2456 base => $main::ldap_server_dn,
2457 scope => 'base',
2458 attrs => 'FAIrepository',
2459 filter => 'objectClass=FAIrepositoryServer'
2460 );
2461 if($mesg->count) {
2462 foreach my $entry(@{$mesg->{'entries'}}) {
2463 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2464 my ($server, $tag, $release, $sections)= split /\|/, $value;
2465 my $line = "deb $server $release";
2466 $sections =~ s/,/ /g;
2467 $line.= " $sections";
2468 print $fh $line."\n";
2469 }
2470 }
2471 }
2472 } else {
2473 if (defined $main::ldap_server_dn){
2474 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2475 } else {
2476 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2477 }
2478 }
2479 close($fh);
2481 return $result;
2482 }
2485 sub run_create_packages_list_db {
2486 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2487 my $session_id = $session->ID;
2489 my $task = POE::Wheel::Run->new(
2490 Priority => +20,
2491 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2492 StdoutEvent => "session_run_result",
2493 StderrEvent => "session_run_debug",
2494 CloseEvent => "session_run_done",
2495 );
2496 $heap->{task}->{ $task->ID } = $task;
2497 }
2500 sub create_packages_list_db {
2501 my ($ldap_handle, $sources_file, $session_id) = @_;
2503 # it should not be possible to trigger a recreation of packages_list_db
2504 # while packages_list_db is under construction, so set flag packages_list_under_construction
2505 # which is tested befor recreation can be started
2506 if (-r $packages_list_under_construction) {
2507 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2508 return;
2509 } else {
2510 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2511 # set packages_list_under_construction to true
2512 system("touch $packages_list_under_construction");
2513 @packages_list_statements=();
2514 }
2516 if (not defined $session_id) { $session_id = 0; }
2517 if (not defined $ldap_handle) {
2518 $ldap_handle= &get_ldap_handle();
2520 if (not defined $ldap_handle) {
2521 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2522 unlink($packages_list_under_construction);
2523 return;
2524 }
2525 }
2526 if (not defined $sources_file) {
2527 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2528 $sources_file = &create_sources_list($session_id);
2529 }
2531 if (not defined $sources_file) {
2532 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2533 unlink($packages_list_under_construction);
2534 return;
2535 }
2537 my $line;
2539 open(CONFIG, "<$sources_file") or do {
2540 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2541 unlink($packages_list_under_construction);
2542 return;
2543 };
2545 # Read lines
2546 while ($line = <CONFIG>){
2547 # Unify
2548 chop($line);
2549 $line =~ s/^\s+//;
2550 $line =~ s/^\s+/ /;
2552 # Strip comments
2553 $line =~ s/#.*$//g;
2555 # Skip empty lines
2556 if ($line =~ /^\s*$/){
2557 next;
2558 }
2560 # Interpret deb line
2561 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2562 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2563 my $section;
2564 foreach $section (split(' ', $sections)){
2565 &parse_package_info( $baseurl, $dist, $section, $session_id );
2566 }
2567 }
2568 }
2570 close (CONFIG);
2572 find(\&cleanup_and_extract, keys( %repo_dirs ));
2573 &main::strip_packages_list_statements();
2574 unshift @packages_list_statements, "VACUUM";
2575 $packages_list_db->exec_statementlist(\@packages_list_statements);
2576 unlink($packages_list_under_construction);
2577 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2578 return;
2579 }
2581 # This function should do some intensive task to minimize the db-traffic
2582 sub strip_packages_list_statements {
2583 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2584 my @new_statement_list=();
2585 my $hash;
2586 my $insert_hash;
2587 my $update_hash;
2588 my $delete_hash;
2589 my $local_timestamp=get_time();
2591 foreach my $existing_entry (@existing_entries) {
2592 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2593 }
2595 foreach my $statement (@packages_list_statements) {
2596 if($statement =~ /^INSERT/i) {
2597 # Assign the values from the insert statement
2598 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2599 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2600 if(exists($hash->{$distribution}->{$package}->{$version})) {
2601 # If section or description has changed, update the DB
2602 if(
2603 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2604 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2605 ) {
2606 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2607 }
2608 } else {
2609 # Insert a non-existing entry to db
2610 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2611 }
2612 } elsif ($statement =~ /^UPDATE/i) {
2613 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2614 /^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;
2615 foreach my $distribution (keys %{$hash}) {
2616 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2617 # update the insertion hash to execute only one query per package (insert instead insert+update)
2618 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2619 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2620 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2621 my $section;
2622 my $description;
2623 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2624 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2625 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2626 }
2627 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2628 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2629 }
2630 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2631 }
2632 }
2633 }
2634 }
2635 }
2637 # TODO: Check for orphaned entries
2639 # unroll the insert_hash
2640 foreach my $distribution (keys %{$insert_hash}) {
2641 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2642 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2643 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2644 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2645 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2646 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2647 ."'$local_timestamp')";
2648 }
2649 }
2650 }
2652 # unroll the update hash
2653 foreach my $distribution (keys %{$update_hash}) {
2654 foreach my $package (keys %{$update_hash->{$distribution}}) {
2655 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2656 my $set = "";
2657 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2658 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2659 }
2660 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2661 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2662 }
2663 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2664 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2665 }
2666 if(defined($set) and length($set) > 0) {
2667 $set .= "timestamp = '$local_timestamp'";
2668 } else {
2669 next;
2670 }
2671 push @new_statement_list,
2672 "UPDATE $main::packages_list_tn SET $set WHERE"
2673 ." distribution = '$distribution'"
2674 ." AND package = '$package'"
2675 ." AND version = '$version'";
2676 }
2677 }
2678 }
2680 @packages_list_statements = @new_statement_list;
2681 }
2684 sub parse_package_info {
2685 my ($baseurl, $dist, $section, $session_id)= @_;
2686 my ($package);
2687 if (not defined $session_id) { $session_id = 0; }
2688 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2689 $repo_dirs{ "${repo_path}/pool" } = 1;
2691 foreach $package ("Packages.gz"){
2692 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2693 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2694 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2695 }
2697 }
2700 sub get_package {
2701 my ($url, $dest, $session_id)= @_;
2702 if (not defined $session_id) { $session_id = 0; }
2704 my $tpath = dirname($dest);
2705 -d "$tpath" || mkpath "$tpath";
2707 # This is ugly, but I've no time to take a look at "how it works in perl"
2708 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2709 system("gunzip -cd '$dest' > '$dest.in'");
2710 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2711 unlink($dest);
2712 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2713 } else {
2714 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2715 }
2716 return 0;
2717 }
2720 sub parse_package {
2721 my ($path, $dist, $srv_path, $session_id)= @_;
2722 if (not defined $session_id) { $session_id = 0;}
2723 my ($package, $version, $section, $description);
2724 my $PACKAGES;
2725 my $timestamp = &get_time();
2727 if(not stat("$path.in")) {
2728 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2729 return;
2730 }
2732 open($PACKAGES, "<$path.in");
2733 if(not defined($PACKAGES)) {
2734 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2735 return;
2736 }
2738 # Read lines
2739 while (<$PACKAGES>){
2740 my $line = $_;
2741 # Unify
2742 chop($line);
2744 # Use empty lines as a trigger
2745 if ($line =~ /^\s*$/){
2746 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2747 push(@packages_list_statements, $sql);
2748 $package = "none";
2749 $version = "none";
2750 $section = "none";
2751 $description = "none";
2752 next;
2753 }
2755 # Trigger for package name
2756 if ($line =~ /^Package:\s/){
2757 ($package)= ($line =~ /^Package: (.*)$/);
2758 next;
2759 }
2761 # Trigger for version
2762 if ($line =~ /^Version:\s/){
2763 ($version)= ($line =~ /^Version: (.*)$/);
2764 next;
2765 }
2767 # Trigger for description
2768 if ($line =~ /^Description:\s/){
2769 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2770 next;
2771 }
2773 # Trigger for section
2774 if ($line =~ /^Section:\s/){
2775 ($section)= ($line =~ /^Section: (.*)$/);
2776 next;
2777 }
2779 # Trigger for filename
2780 if ($line =~ /^Filename:\s/){
2781 my ($filename) = ($line =~ /^Filename: (.*)$/);
2782 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2783 next;
2784 }
2785 }
2787 close( $PACKAGES );
2788 unlink( "$path.in" );
2789 }
2792 sub store_fileinfo {
2793 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2795 my %fileinfo = (
2796 'package' => $package,
2797 'dist' => $dist,
2798 'version' => $vers,
2799 );
2801 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2802 }
2805 sub cleanup_and_extract {
2806 my $fileinfo = $repo_files{ $File::Find::name };
2808 if( defined $fileinfo ) {
2810 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2811 my $sql;
2812 my $package = $fileinfo->{ 'package' };
2813 my $newver = $fileinfo->{ 'version' };
2815 mkpath($dir);
2816 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2818 if( -f "$dir/DEBIAN/templates" ) {
2820 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2822 my $tmpl= "";
2823 {
2824 local $/=undef;
2825 open FILE, "$dir/DEBIAN/templates";
2826 $tmpl = &encode_base64(<FILE>);
2827 close FILE;
2828 }
2829 rmtree("$dir/DEBIAN/templates");
2831 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2832 push @packages_list_statements, $sql;
2833 }
2834 }
2836 return;
2837 }
2840 sub register_at_foreign_servers {
2841 my ($kernel) = $_[KERNEL];
2843 # hole alle bekannten server aus known_server_db
2844 my $server_sql = "SELECT * FROM $known_server_tn";
2845 my $server_res = $known_server_db->exec_statement($server_sql);
2847 # no entries in known_server_db
2848 if (not ref(@$server_res[0]) eq "ARRAY") {
2849 # TODO
2850 }
2852 # detect already connected clients
2853 my $client_sql = "SELECT * FROM $known_clients_tn";
2854 my $client_res = $known_clients_db->exec_statement($client_sql);
2856 # send my server details to all other gosa-si-server within the network
2857 foreach my $hit (@$server_res) {
2858 my $hostname = @$hit[0];
2859 my $hostkey = &create_passwd;
2861 # add already connected clients to registration message
2862 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2863 &add_content2xml_hash($myhash, 'key', $hostkey);
2864 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2866 # build registration message and send it
2867 my $foreign_server_msg = &create_xml_string($myhash);
2868 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2869 }
2871 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2872 return;
2873 }
2876 #==== MAIN = main ==============================================================
2877 # parse commandline options
2878 Getopt::Long::Configure( "bundling" );
2879 GetOptions("h|help" => \&usage,
2880 "c|config=s" => \$cfg_file,
2881 "f|foreground" => \$foreground,
2882 "v|verbose+" => \$verbose,
2883 "no-arp+" => \$no_arp,
2884 );
2886 # read and set config parameters
2887 &check_cmdline_param ;
2888 &read_configfile;
2889 &check_pid;
2891 $SIG{CHLD} = 'IGNORE';
2893 # forward error messages to logfile
2894 if( ! $foreground ) {
2895 open( STDIN, '+>/dev/null' );
2896 open( STDOUT, '+>&STDIN' );
2897 open( STDERR, '+>&STDIN' );
2898 }
2900 # Just fork, if we are not in foreground mode
2901 if( ! $foreground ) {
2902 chdir '/' or die "Can't chdir to /: $!";
2903 $pid = fork;
2904 setsid or die "Can't start a new session: $!";
2905 umask 0;
2906 } else {
2907 $pid = $$;
2908 }
2910 # Do something useful - put our PID into the pid_file
2911 if( 0 != $pid ) {
2912 open( LOCK_FILE, ">$pid_file" );
2913 print LOCK_FILE "$pid\n";
2914 close( LOCK_FILE );
2915 if( !$foreground ) {
2916 exit( 0 )
2917 };
2918 }
2920 # parse head url and revision from svn
2921 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2922 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2923 $server_headURL = defined $1 ? $1 : 'unknown' ;
2924 $server_revision = defined $2 ? $2 : 'unknown' ;
2925 if ($server_headURL =~ /\/tag\// ||
2926 $server_headURL =~ /\/branches\// ) {
2927 $server_status = "stable";
2928 } else {
2929 $server_status = "developmental" ;
2930 }
2933 daemon_log(" ", 1);
2934 daemon_log("$0 started!", 1);
2935 daemon_log("status: $server_status", 1);
2936 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2938 # connect to incoming_db
2939 unlink($incoming_file_name);
2940 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2941 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2943 # connect to gosa-si job queue
2944 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2945 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2947 # connect to known_clients_db
2948 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2949 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2951 # connect to foreign_clients_db
2952 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2953 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2955 # connect to known_server_db
2956 unlink($known_server_file_name);
2957 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2958 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2960 # connect to login_usr_db
2961 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2962 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2964 # connect to fai_server_db and fai_release_db
2965 unlink($fai_server_file_name);
2966 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2967 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2969 unlink($fai_release_file_name);
2970 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2971 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2973 # connect to packages_list_db
2974 #unlink($packages_list_file_name);
2975 unlink($packages_list_under_construction);
2976 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2977 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2979 # connect to messaging_db
2980 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2981 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2984 # create xml object used for en/decrypting
2985 $xml = new XML::Simple();
2988 # foreign servers
2989 my @foreign_server_list;
2991 # add foreign server from cfg file
2992 if ($foreign_server_string ne "") {
2993 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2994 foreach my $foreign_server (@cfg_foreign_server_list) {
2995 push(@foreign_server_list, $foreign_server);
2996 }
2997 }
2999 # add foreign server from dns
3000 my @tmp_servers;
3001 if ( !$server_domain) {
3002 # Try our DNS Searchlist
3003 for my $domain(get_dns_domains()) {
3004 chomp($domain);
3005 my @tmp_domains= &get_server_addresses($domain);
3006 if(@tmp_domains) {
3007 for my $tmp_server(@tmp_domains) {
3008 push @tmp_servers, $tmp_server;
3009 }
3010 }
3011 }
3012 if(@tmp_servers && length(@tmp_servers)==0) {
3013 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3014 }
3015 } else {
3016 @tmp_servers = &get_server_addresses($server_domain);
3017 if( 0 == @tmp_servers ) {
3018 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3019 }
3020 }
3021 foreach my $server (@tmp_servers) {
3022 unshift(@foreign_server_list, $server);
3023 }
3024 # eliminate duplicate entries
3025 @foreign_server_list = &del_doubles(@foreign_server_list);
3026 my $all_foreign_server = join(", ", @foreign_server_list);
3027 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
3029 # add all found foreign servers to known_server
3030 my $act_timestamp = &get_time();
3031 foreach my $foreign_server (@foreign_server_list) {
3033 # do not add myself to known_server_db
3034 if (&is_local($foreign_server)) { next; }
3035 ######################################
3037 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3038 primkey=>['hostname'],
3039 hostname=>$foreign_server,
3040 status=>'not_jet_registered',
3041 hostkey=>"none",
3042 timestamp=>$act_timestamp,
3043 } );
3044 }
3047 POE::Component::Server::TCP->new(
3048 Alias => "TCP_SERVER",
3049 Port => $server_port,
3050 ClientInput => sub {
3051 my ($kernel, $input) = @_[KERNEL, ARG0];
3052 push(@tasks, $input);
3053 push(@msgs_to_decrypt, $input);
3054 $kernel->yield("msg_to_decrypt");
3055 },
3056 InlineStates => {
3057 msg_to_decrypt => \&msg_to_decrypt,
3058 next_task => \&next_task,
3059 task_result => \&handle_task_result,
3060 task_done => \&handle_task_done,
3061 task_debug => \&handle_task_debug,
3062 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3063 }
3064 );
3066 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3068 # create session for repeatedly checking the job queue for jobs
3069 POE::Session->create(
3070 inline_states => {
3071 _start => \&session_start,
3072 register_at_foreign_servers => \®ister_at_foreign_servers,
3073 sig_handler => \&sig_handler,
3074 next_task => \&next_task,
3075 task_result => \&handle_task_result,
3076 task_done => \&handle_task_done,
3077 task_debug => \&handle_task_debug,
3078 watch_for_next_tasks => \&watch_for_next_tasks,
3079 watch_for_new_messages => \&watch_for_new_messages,
3080 watch_for_delivery_messages => \&watch_for_delivery_messages,
3081 watch_for_done_messages => \&watch_for_done_messages,
3082 watch_for_new_jobs => \&watch_for_new_jobs,
3083 watch_for_modified_jobs => \&watch_for_modified_jobs,
3084 watch_for_done_jobs => \&watch_for_done_jobs,
3085 watch_for_old_known_clients => \&watch_for_old_known_clients,
3086 create_packages_list_db => \&run_create_packages_list_db,
3087 create_fai_server_db => \&run_create_fai_server_db,
3088 create_fai_release_db => \&run_create_fai_release_db,
3089 recreate_packages_db => \&run_recreate_packages_db,
3090 session_run_result => \&session_run_result,
3091 session_run_debug => \&session_run_debug,
3092 session_run_done => \&session_run_done,
3093 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3094 }
3095 );
3098 # import all modules
3099 &import_modules;
3101 # TODO
3102 # check wether all modules are gosa-si valid passwd check
3106 POE::Kernel->run();
3107 exit;