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