ebe24c107b76136385153977ee3ac330b4b539b5
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 ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file,
86 $server_ip, $server_port, $ClientPackages_key,
87 $arp_activ, $gosa_unit_tag,
88 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_bus = 0;
115 $bus_activ = "true";
116 $no_arp = 0;
117 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
118 my @packages_list_statements;
119 my $watch_for_new_jobs_in_progress = 0;
121 # holds all incoming decrypted messages
122 our $incoming_db;
123 our $incoming_tn = 'incoming';
124 my $incoming_file_name;
125 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
126 "timestamp DEFAULT 'none'",
127 "headertag DEFAULT 'none'",
128 "targettag DEFAULT 'none'",
129 "xmlmessage DEFAULT 'none'",
130 "module DEFAULT 'none'",
131 "sessionid DEFAULT '0'",
132 );
134 # holds all gosa jobs
135 our $job_db;
136 our $job_queue_tn = 'jobs';
137 my $job_queue_file_name;
138 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
139 "timestamp DEFAULT 'none'",
140 "status DEFAULT 'none'",
141 "result DEFAULT 'none'",
142 "progress DEFAULT 'none'",
143 "headertag DEFAULT 'none'",
144 "targettag DEFAULT 'none'",
145 "xmlmessage DEFAULT 'none'",
146 "macaddress DEFAULT 'none'",
147 "plainname DEFAULT 'none'",
148 );
150 # holds all other gosa-sd as well as the gosa-sd-bus
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients";
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server";
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release";
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386";
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging";
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
197 "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai";
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 %cfg_defaults = (
210 "general" => {
211 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
212 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
213 },
214 "bus" => {
215 "activ" => [\$bus_activ, "true"],
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 },
237 "GOsaPackages" => {
238 "ip" => [\$gosa_ip, "0.0.0.0"],
239 "port" => [\$gosa_port, "20082"],
240 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
241 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
242 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
243 "key" => [\$GosaPackages_key, "none"],
244 },
245 "ClientPackages" => {
246 "key" => [\$ClientPackages_key, "none"],
247 },
248 "ServerPackages"=> {
249 "address" => [\$foreign_server_string, ""],
250 "domain" => [\$server_domain, ""],
251 "key" => [\$ServerPackages_key, "none"],
252 "key-lifetime" => [\$foreign_servers_register_delay, 120],
253 }
254 );
257 #=== FUNCTION ================================================================
258 # NAME: usage
259 # PARAMETERS: nothing
260 # RETURNS: nothing
261 # DESCRIPTION: print out usage text to STDERR
262 #===============================================================================
263 sub usage {
264 print STDERR << "EOF" ;
265 usage: $prg [-hvf] [-c config]
267 -h : this (help) message
268 -c <file> : config file
269 -f : foreground, process will not be forked to background
270 -v : be verbose (multiple to increase verbosity)
271 -no-bus : starts $prg without connection to bus
272 -no-arp : starts $prg without connection to arp module
274 EOF
275 print "\n" ;
276 }
279 #=== FUNCTION ================================================================
280 # NAME: read_configfile
281 # PARAMETERS: cfg_file - string -
282 # RETURNS: nothing
283 # DESCRIPTION: read cfg_file and set variables
284 #===============================================================================
285 sub read_configfile {
286 my $cfg;
287 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
288 if( -r $cfg_file ) {
289 $cfg = Config::IniFiles->new( -file => $cfg_file );
290 } else {
291 print STDERR "Couldn't read config file!\n";
292 }
293 } else {
294 $cfg = Config::IniFiles->new() ;
295 }
296 foreach my $section (keys %cfg_defaults) {
297 foreach my $param (keys %{$cfg_defaults{ $section }}) {
298 my $pinfo = $cfg_defaults{ $section }{ $param };
299 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
300 }
301 }
302 }
305 #=== FUNCTION ================================================================
306 # NAME: logging
307 # PARAMETERS: level - string - default 'info'
308 # msg - string -
309 # facility - string - default 'LOG_DAEMON'
310 # RETURNS: nothing
311 # DESCRIPTION: function for logging
312 #===============================================================================
313 sub daemon_log {
314 # log into log_file
315 my( $msg, $level ) = @_;
316 if(not defined $msg) { return }
317 if(not defined $level) { $level = 1 }
318 if(defined $log_file){
319 open(LOG_HANDLE, ">>$log_file");
320 chmod 0600, $log_file;
321 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
322 print STDERR "cannot open $log_file: $!";
323 return
324 }
325 chomp($msg);
326 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
327 if($level <= $verbose){
328 my ($seconds, $minutes, $hours, $monthday, $month,
329 $year, $weekday, $yearday, $sommertime) = localtime(time);
330 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
331 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
332 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
333 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
334 $month = $monthnames[$month];
335 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
336 $year+=1900;
337 my $name = $prg;
339 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
340 print LOG_HANDLE $log_msg;
341 if( $foreground ) {
342 print STDERR $log_msg;
343 }
344 }
345 close( LOG_HANDLE );
346 }
347 }
350 #=== FUNCTION ================================================================
351 # NAME: check_cmdline_param
352 # PARAMETERS: nothing
353 # RETURNS: nothing
354 # DESCRIPTION: validates commandline parameter
355 #===============================================================================
356 sub check_cmdline_param () {
357 my $err_config;
358 my $err_counter = 0;
359 if(not defined($cfg_file)) {
360 $cfg_file = "/etc/gosa-si/server.conf";
361 if(! -r $cfg_file) {
362 $err_config = "please specify a config file";
363 $err_counter += 1;
364 }
365 }
366 if( $err_counter > 0 ) {
367 &usage( "", 1 );
368 if( defined( $err_config)) { print STDERR "$err_config\n"}
369 print STDERR "\n";
370 exit( -1 );
371 }
372 }
375 #=== FUNCTION ================================================================
376 # NAME: check_pid
377 # PARAMETERS: nothing
378 # RETURNS: nothing
379 # DESCRIPTION: handels pid processing
380 #===============================================================================
381 sub check_pid {
382 $pid = -1;
383 # Check, if we are already running
384 if( open(LOCK_FILE, "<$pid_file") ) {
385 $pid = <LOCK_FILE>;
386 if( defined $pid ) {
387 chomp( $pid );
388 if( -f "/proc/$pid/stat" ) {
389 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
390 if( $stat ) {
391 daemon_log("ERROR: Already running",1);
392 close( LOCK_FILE );
393 exit -1;
394 }
395 }
396 }
397 close( LOCK_FILE );
398 unlink( $pid_file );
399 }
401 # create a syslog msg if it is not to possible to open PID file
402 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
403 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
404 if (open(LOCK_FILE, '<', $pid_file)
405 && ($pid = <LOCK_FILE>))
406 {
407 chomp($pid);
408 $msg .= "(PID $pid)\n";
409 } else {
410 $msg .= "(unable to read PID)\n";
411 }
412 if( ! ($foreground) ) {
413 openlog( $0, "cons,pid", "daemon" );
414 syslog( "warning", $msg );
415 closelog();
416 }
417 else {
418 print( STDERR " $msg " );
419 }
420 exit( -1 );
421 }
422 }
424 #=== FUNCTION ================================================================
425 # NAME: import_modules
426 # PARAMETERS: module_path - string - abs. path to the directory the modules
427 # are stored
428 # RETURNS: nothing
429 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
430 # state is on is imported by "require 'file';"
431 #===============================================================================
432 sub import_modules {
433 daemon_log(" ", 1);
435 if (not -e $modules_path) {
436 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
437 }
439 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
440 while (defined (my $file = readdir (DIR))) {
441 if (not $file =~ /(\S*?).pm$/) {
442 next;
443 }
444 my $mod_name = $1;
446 if( $file =~ /ArpHandler.pm/ ) {
447 if( $no_arp > 0 ) {
448 next;
449 }
450 }
452 eval { require $file; };
453 if ($@) {
454 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
455 daemon_log("$@", 5);
456 } else {
457 my $info = eval($mod_name.'::get_module_info()');
458 # Only load module if get_module_info() returns a non-null object
459 if( $info ) {
460 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
461 $known_modules->{$mod_name} = $info;
462 daemon_log("0 INFO: module $mod_name loaded", 5);
463 }
464 }
465 }
466 close (DIR);
467 }
470 #=== FUNCTION ================================================================
471 # NAME: sig_int_handler
472 # PARAMETERS: signal - string - signal arose from system
473 # RETURNS: noting
474 # DESCRIPTION: handels tasks to be done befor signal becomes active
475 #===============================================================================
476 sub sig_int_handler {
477 my ($signal) = @_;
479 # if (defined($ldap_handle)) {
480 # $ldap_handle->disconnect;
481 # }
482 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
485 daemon_log("shutting down gosa-si-server", 1);
486 system("kill `ps -C gosa-si-server-nobus -o pid=`");
487 }
488 $SIG{INT} = \&sig_int_handler;
491 sub check_key_and_xml_validity {
492 my ($crypted_msg, $module_key, $session_id) = @_;
493 my $msg;
494 my $msg_hash;
495 my $error_string;
496 eval{
497 $msg = &decrypt_msg($crypted_msg, $module_key);
499 if ($msg =~ /<xml>/i){
500 $msg =~ s/\s+/ /g; # just for better daemon_log
501 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
502 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
504 ##############
505 # check header
506 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
507 my $header_l = $msg_hash->{'header'};
508 if( 1 > @{$header_l} ) { die 'empty header tag'; }
509 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
510 my $header = @{$header_l}[0];
511 if( 0 == length $header) { die 'empty string in header tag'; }
513 ##############
514 # check source
515 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
516 my $source_l = $msg_hash->{'source'};
517 if( 1 > @{$source_l} ) { die 'empty source tag'; }
518 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
519 my $source = @{$source_l}[0];
520 if( 0 == length $source) { die 'source error'; }
522 ##############
523 # check target
524 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
525 my $target_l = $msg_hash->{'target'};
526 if( 1 > @{$target_l} ) { die 'empty target tag'; }
527 }
528 };
529 if($@) {
530 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
531 $msg = undef;
532 $msg_hash = undef;
533 }
535 return ($msg, $msg_hash);
536 }
539 sub check_outgoing_xml_validity {
540 my ($msg) = @_;
542 my $msg_hash;
543 eval{
544 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
546 ##############
547 # check header
548 my $header_l = $msg_hash->{'header'};
549 if( 1 != @{$header_l} ) {
550 die 'no or more than one headers specified';
551 }
552 my $header = @{$header_l}[0];
553 if( 0 == length $header) {
554 die 'header has length 0';
555 }
557 ##############
558 # check source
559 my $source_l = $msg_hash->{'source'};
560 if( 1 != @{$source_l} ) {
561 die 'no or more than 1 sources specified';
562 }
563 my $source = @{$source_l}[0];
564 if( 0 == length $source) {
565 die 'source has length 0';
566 }
567 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
568 $source =~ /^GOSA$/i ) {
569 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
570 }
572 ##############
573 # check target
574 my $target_l = $msg_hash->{'target'};
575 if( 0 == @{$target_l} ) {
576 die "no targets specified";
577 }
578 foreach my $target (@$target_l) {
579 if( 0 == length $target) {
580 die "target has length 0";
581 }
582 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
583 $target =~ /^GOSA$/i ||
584 $target =~ /^\*$/ ||
585 $target =~ /KNOWN_SERVER/i ||
586 $target =~ /JOBDB/i ||
587 $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 ){
588 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
589 }
590 }
591 };
592 if($@) {
593 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
594 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
595 $msg_hash = undef;
596 }
598 return ($msg_hash);
599 }
602 sub input_from_known_server {
603 my ($input, $remote_ip, $session_id) = @_ ;
604 my ($msg, $msg_hash, $module);
606 my $sql_statement= "SELECT * FROM known_server";
607 my $query_res = $known_server_db->select_dbentry( $sql_statement );
609 while( my ($hit_num, $hit) = each %{ $query_res } ) {
610 my $host_name = $hit->{hostname};
611 if( not $host_name =~ "^$remote_ip") {
612 next;
613 }
614 my $host_key = $hit->{hostkey};
615 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
616 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
618 # check if module can open msg envelope with module key
619 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
620 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
621 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
622 daemon_log("$@", 8);
623 next;
624 }
625 else {
626 $msg = $tmp_msg;
627 $msg_hash = $tmp_msg_hash;
628 $module = "ServerPackages";
629 last;
630 }
631 }
633 if( (!$msg) || (!$msg_hash) || (!$module) ) {
634 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
635 }
637 return ($msg, $msg_hash, $module);
638 }
641 sub input_from_known_client {
642 my ($input, $remote_ip, $session_id) = @_ ;
643 my ($msg, $msg_hash, $module);
645 my $sql_statement= "SELECT * FROM known_clients";
646 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
647 while( my ($hit_num, $hit) = each %{ $query_res } ) {
648 my $host_name = $hit->{hostname};
649 if( not $host_name =~ /^$remote_ip:\d*$/) {
650 next;
651 }
652 my $host_key = $hit->{hostkey};
653 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
654 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
656 # check if module can open msg envelope with module key
657 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
659 if( (!$msg) || (!$msg_hash) ) {
660 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
661 &daemon_log("$@", 8);
662 next;
663 }
664 else {
665 $module = "ClientPackages";
666 last;
667 }
668 }
670 if( (!$msg) || (!$msg_hash) || (!$module) ) {
671 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
672 }
674 return ($msg, $msg_hash, $module);
675 }
678 sub input_from_unknown_host {
679 no strict "refs";
680 my ($input, $session_id) = @_ ;
681 my ($msg, $msg_hash, $module);
682 my $error_string;
684 my %act_modules = %$known_modules;
686 while( my ($mod, $info) = each(%act_modules)) {
688 # check a key exists for this module
689 my $module_key = ${$mod."_key"};
690 if( not defined $module_key ) {
691 if( $mod eq 'ArpHandler' ) {
692 next;
693 }
694 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
695 next;
696 }
697 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
699 # check if module can open msg envelope with module key
700 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
701 if( (not defined $msg) || (not defined $msg_hash) ) {
702 next;
703 }
704 else {
705 $module = $mod;
706 last;
707 }
708 }
710 if( (!$msg) || (!$msg_hash) || (!$module)) {
711 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
712 }
714 return ($msg, $msg_hash, $module);
715 }
718 sub create_ciphering {
719 my ($passwd) = @_;
720 if((!defined($passwd)) || length($passwd)==0) {
721 $passwd = "";
722 }
723 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
724 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
725 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
726 $my_cipher->set_iv($iv);
727 return $my_cipher;
728 }
731 sub encrypt_msg {
732 my ($msg, $key) = @_;
733 my $my_cipher = &create_ciphering($key);
734 my $len;
735 {
736 use bytes;
737 $len= 16-length($msg)%16;
738 }
739 $msg = "\0"x($len).$msg;
740 $msg = $my_cipher->encrypt($msg);
741 chomp($msg = &encode_base64($msg));
742 # there are no newlines allowed inside msg
743 $msg=~ s/\n//g;
744 return $msg;
745 }
748 sub decrypt_msg {
750 my ($msg, $key) = @_ ;
751 $msg = &decode_base64($msg);
752 my $my_cipher = &create_ciphering($key);
753 $msg = $my_cipher->decrypt($msg);
754 $msg =~ s/\0*//g;
755 return $msg;
756 }
759 sub get_encrypt_key {
760 my ($target) = @_ ;
761 my $encrypt_key;
762 my $error = 0;
764 # target can be in known_server
765 if( not defined $encrypt_key ) {
766 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
767 my $query_res = $known_server_db->select_dbentry( $sql_statement );
768 while( my ($hit_num, $hit) = each %{ $query_res } ) {
769 my $host_name = $hit->{hostname};
770 if( $host_name ne $target ) {
771 next;
772 }
773 $encrypt_key = $hit->{hostkey};
774 last;
775 }
776 }
778 # target can be in known_client
779 if( not defined $encrypt_key ) {
780 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
781 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
782 while( my ($hit_num, $hit) = each %{ $query_res } ) {
783 my $host_name = $hit->{hostname};
784 if( $host_name ne $target ) {
785 next;
786 }
787 $encrypt_key = $hit->{hostkey};
788 last;
789 }
790 }
792 return $encrypt_key;
793 }
796 #=== FUNCTION ================================================================
797 # NAME: open_socket
798 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
799 # [PeerPort] string necessary if port not appended by PeerAddr
800 # RETURNS: socket IO::Socket::INET
801 # DESCRIPTION: open a socket to PeerAddr
802 #===============================================================================
803 sub open_socket {
804 my ($PeerAddr, $PeerPort) = @_ ;
805 if(defined($PeerPort)){
806 $PeerAddr = $PeerAddr.":".$PeerPort;
807 }
808 my $socket;
809 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
810 Porto => "tcp",
811 Type => SOCK_STREAM,
812 Timeout => 5,
813 );
814 if(not defined $socket) {
815 return;
816 }
817 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
818 return $socket;
819 }
822 #=== FUNCTION ================================================================
823 # NAME: get_ip
824 # PARAMETERS: interface name (i.e. eth0)
825 # RETURNS: (ip address)
826 # DESCRIPTION: Uses ioctl to get ip address directly from system.
827 #===============================================================================
828 sub get_ip {
829 my $ifreq= shift;
830 my $result= "";
831 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
832 my $proto= getprotobyname('ip');
834 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
835 or die "socket: $!";
837 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
838 my ($if, $sin) = unpack 'a16 a16', $ifreq;
839 my ($port, $addr) = sockaddr_in $sin;
840 my $ip = inet_ntoa $addr;
842 if ($ip && length($ip) > 0) {
843 $result = $ip;
844 }
845 }
847 return $result;
848 }
851 sub get_local_ip_for_remote_ip {
852 my $remote_ip= shift;
853 my $result="0.0.0.0";
855 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
856 if($remote_ip eq "127.0.0.1") {
857 $result = "127.0.0.1";
858 } else {
859 my $PROC_NET_ROUTE= ('/proc/net/route');
861 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
862 or die "Could not open $PROC_NET_ROUTE";
864 my @ifs = <PROC_NET_ROUTE>;
866 close(PROC_NET_ROUTE);
868 # Eat header line
869 shift @ifs;
870 chomp @ifs;
871 foreach my $line(@ifs) {
872 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
873 my $destination;
874 my $mask;
875 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
876 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
877 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
878 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
879 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
880 # destination matches route, save mac and exit
881 $result= &get_ip($Iface);
882 last;
883 }
884 }
885 }
886 } else {
887 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
888 }
889 return $result;
890 }
893 sub send_msg_to_target {
894 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
895 my $error = 0;
896 my $header;
897 my $timestamp = &get_time();
898 my $new_status;
899 my $act_status;
900 my ($sql_statement, $res);
902 if( $msg_header ) {
903 $header = "'$msg_header'-";
904 } else {
905 $header = "";
906 }
908 # Patch the source ip
909 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
910 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
911 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
912 }
914 # encrypt xml msg
915 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
917 # opensocket
918 my $socket = &open_socket($address);
919 if( !$socket ) {
920 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
921 $error++;
922 }
924 if( $error == 0 ) {
925 # send xml msg
926 print $socket $crypted_msg."\n";
928 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
929 daemon_log("$session_id DEBUG: message:\n$msg", 9);
931 }
933 # close socket in any case
934 if( $socket ) {
935 close $socket;
936 }
938 if( $error > 0 ) { $new_status = "down"; }
939 else { $new_status = $msg_header; }
942 # known_clients
943 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
944 $res = $known_clients_db->select_dbentry($sql_statement);
945 if( keys(%$res) == 1) {
946 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
947 if ($act_status eq "down" && $new_status eq "down") {
948 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
949 $res = $known_clients_db->del_dbentry($sql_statement);
950 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
951 } else {
952 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
953 $res = $known_clients_db->update_dbentry($sql_statement);
954 if($new_status eq "down"){
955 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
956 } else {
957 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
958 }
959 }
960 }
962 # known_server
963 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
964 $res = $known_server_db->select_dbentry($sql_statement);
965 if( keys(%$res) == 1) {
966 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967 if ($act_status eq "down" && $new_status eq "down") {
968 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
969 $res = $known_server_db->del_dbentry($sql_statement);
970 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
971 }
972 else {
973 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
974 $res = $known_server_db->update_dbentry($sql_statement);
975 if($new_status eq "down"){
976 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
977 } else {
978 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
979 }
980 }
981 }
982 return $error;
983 }
986 sub update_jobdb_status_for_send_msgs {
987 my ($answer, $error) = @_;
988 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
989 my $jobdb_id = $1;
991 # sending msg faild
992 if( $error ) {
993 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
994 my $sql_statement = "UPDATE $job_queue_tn ".
995 "SET status='error', result='can not deliver msg, please consult log file' ".
996 "WHERE id=$jobdb_id";
997 my $res = $job_db->update_dbentry($sql_statement);
998 }
1000 # sending msg was successful
1001 } else {
1002 my $sql_statement = "UPDATE $job_queue_tn ".
1003 "SET status='done' ".
1004 "WHERE id=$jobdb_id AND status='processed'";
1005 my $res = $job_db->update_dbentry($sql_statement);
1006 }
1007 }
1008 }
1011 sub sig_handler {
1012 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013 daemon_log("0 INFO got signal '$signal'", 1);
1014 $kernel->sig_handled();
1015 return;
1016 }
1019 sub msg_to_decrypt {
1020 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1021 my $session_id = $session->ID;
1022 my ($msg, $msg_hash, $module);
1023 my $error = 0;
1025 # hole neue msg aus @msgs_to_decrypt
1026 my $next_msg = shift @msgs_to_decrypt;
1028 # entschlüssle sie
1030 # msg is from a new client or gosa
1031 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032 # msg is from a gosa-si-server or gosa-si-bus
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035 }
1036 # msg is from a gosa-si-client
1037 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039 }
1040 # an error occurred
1041 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043 # could not understand a msg from its server the client cause a re-registering process
1044 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1045 "' to cause a re-registering of the client if necessary", 5);
1046 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1047 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1048 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1049 my $host_name = $hit->{'hostname'};
1050 my $host_key = $hit->{'hostkey'};
1051 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1052 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1053 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1054 }
1055 $error++;
1056 }
1059 my $header;
1060 my $target;
1061 my $source;
1062 my $done = 0;
1063 my $sql;
1064 my $res;
1065 # check whether this message should be processed here
1066 if ($error == 0) {
1067 $header = @{$msg_hash->{'header'}}[0];
1068 $target = @{$msg_hash->{'target'}}[0];
1069 $source = @{$msg_hash->{'source'}}[0];
1071 # target and source is equal to GOSA -> process here
1072 if (not $done) {
1073 if ($target eq "GOSA" && $source eq "GOSA") {
1074 $done = 1;
1075 }
1076 }
1078 # target is own address without forward_to_gosa-tag -> process here
1079 if (not $done) {
1080 if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1081 $done = 1;
1082 if ($source eq "GOSA") {
1083 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1084 }
1085 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1086 }
1087 }
1089 # target is a client address in known_clients -> process here
1090 if (not $done) {
1091 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1092 $res = $known_clients_db->select_dbentry($sql);
1093 if (keys(%$res) > 0) {
1094 $done = 1;
1095 my $hostname = $res->{1}->{'hostname'};
1096 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1097 print STDERR "target is a client address in known_clients -> process here\n";
1098 }
1099 }
1101 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1102 if (not $done) {
1103 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1104 my $gosa_at;
1105 my $gosa_session_id;
1106 if (($target eq $server_address) && (defined $forward_to_gosa)){
1107 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1108 if ($gosa_at ne $server_address) {
1109 $done = 1;
1110 print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1111 }
1112 }
1113 }
1115 # if message should be processed here -> add message to incoming_db
1116 if ($done) {
1118 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1119 # so gosa-si-server knows how to process this kind of messages
1120 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1121 $module = "GosaPackages";
1122 }
1124 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1125 primkey=>[],
1126 headertag=>$header,
1127 targettag=>$target,
1128 xmlmessage=>$msg,
1129 timestamp=>&get_time,
1130 module=>$module,
1131 sessionid=>$session_id,
1132 } );
1134 }
1136 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1137 if (not $done) {
1138 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1139 my $gosa_at;
1140 my $gosa_session_id;
1141 if (($target eq $server_address) && (defined $forward_to_gosa)){
1142 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1143 if ($gosa_at eq $server_address) {
1144 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1145 if( defined $session_reference ) {
1146 $heap = $session_reference->get_heap();
1147 }
1148 if(exists $heap->{'client'}) {
1149 $msg = &encrypt_msg($msg, $GosaPackages_key);
1150 $heap->{'client'}->put($msg);
1151 }
1152 $done = 1;
1153 print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1154 }
1155 }
1157 }
1159 # target is a client address in foreign_clients -> forward to registration server
1160 if (not $done) {
1161 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1162 $res = $foreign_clients_db->select_dbentry($sql);
1163 if (keys(%$res) > 0) {
1164 my $hostname = $res->{1}->{'hostname'};
1165 my $regserver = $res->{1}->{'regserver'};
1166 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1167 my $res = $known_server_db->select_dbentry($sql);
1168 if (keys(%$res) > 0) {
1169 my $regserver_key = $res->{1}->{'hostkey'};
1170 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1171 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1172 if ($source eq "GOSA") {
1173 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1174 }
1175 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1176 }
1177 $done = 1;
1178 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1179 }
1180 }
1182 # target is a server address -> forward to server
1183 if (not $done) {
1184 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1185 $res = $known_server_db->select_dbentry($sql);
1186 if (keys(%$res) > 0) {
1187 my $hostkey = $res->{1}->{'hostkey'};
1189 if ($source eq "GOSA") {
1190 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1191 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1193 }
1195 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1196 $done = 1;
1197 print STDERR "target is a server address -> forward to server\n";
1198 }
1201 }
1203 if (not $done) {
1204 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1205 }
1206 }
1208 return;
1209 }
1212 sub next_task {
1213 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1214 my $running_task = POE::Wheel::Run->new(
1215 Program => sub { process_task($session, $heap, $task) },
1216 StdioFilter => POE::Filter::Reference->new(),
1217 StdoutEvent => "task_result",
1218 StderrEvent => "task_debug",
1219 CloseEvent => "task_done",
1220 );
1221 $heap->{task}->{ $running_task->ID } = $running_task;
1222 }
1224 sub handle_task_result {
1225 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1226 my $client_answer = $result->{'answer'};
1227 if( $client_answer =~ s/session_id=(\d+)$// ) {
1228 my $session_id = $1;
1229 if( defined $session_id ) {
1230 my $session_reference = $kernel->ID_id_to_session($session_id);
1231 if( defined $session_reference ) {
1232 $heap = $session_reference->get_heap();
1233 }
1234 }
1236 if(exists $heap->{'client'}) {
1237 $heap->{'client'}->put($client_answer);
1238 }
1239 }
1240 $kernel->sig(CHLD => "child_reap");
1241 }
1243 sub handle_task_debug {
1244 my $result = $_[ARG0];
1245 print STDERR "$result\n";
1246 }
1248 sub handle_task_done {
1249 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1250 delete $heap->{task}->{$task_id};
1251 }
1253 sub process_task {
1254 no strict "refs";
1255 my ($session, $heap, $task) = @_;
1256 my $error = 0;
1257 my $answer_l;
1258 my ($answer_header, @answer_target_l, $answer_source);
1259 my $client_answer = "";
1261 # prepare all variables needed to process message
1262 my $msg = $task->{'xmlmessage'};
1263 my $incoming_id = $task->{'id'};
1264 my $module = $task->{'module'};
1265 my $header = $task->{'headertag'};
1266 my $session_id = $task->{'sessionid'};
1267 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1268 my $source = @{$msg_hash->{'source'}}[0];
1270 # set timestamp of incoming client uptodate, so client will not
1271 # be deleted from known_clients because of expiration
1272 my $act_time = &get_time();
1273 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1274 my $res = $known_clients_db->exec_statement($sql);
1276 ######################
1277 # process incoming msg
1278 if( $error == 0) {
1279 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1280 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1281 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1283 if ( 0 < @{$answer_l} ) {
1284 my $answer_str = join("\n", @{$answer_l});
1285 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1286 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1287 }
1288 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1289 } else {
1290 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1291 }
1293 }
1294 if( !$answer_l ) { $error++ };
1296 ########
1297 # answer
1298 if( $error == 0 ) {
1300 foreach my $answer ( @{$answer_l} ) {
1301 # check outgoing msg to xml validity
1302 my $answer_hash = &check_outgoing_xml_validity($answer);
1303 if( not defined $answer_hash ) { next; }
1305 $answer_header = @{$answer_hash->{'header'}}[0];
1306 @answer_target_l = @{$answer_hash->{'target'}};
1307 $answer_source = @{$answer_hash->{'source'}}[0];
1309 # deliver msg to all targets
1310 foreach my $answer_target ( @answer_target_l ) {
1312 # targets of msg are all gosa-si-clients in known_clients_db
1313 if( $answer_target eq "*" ) {
1314 # answer is for all clients
1315 my $sql_statement= "SELECT * FROM known_clients";
1316 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1317 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1318 my $host_name = $hit->{hostname};
1319 my $host_key = $hit->{hostkey};
1320 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1321 &update_jobdb_status_for_send_msgs($answer, $error);
1322 }
1323 }
1325 # targets of msg are all gosa-si-server in known_server_db
1326 elsif( $answer_target eq "KNOWN_SERVER" ) {
1327 # answer is for all server in known_server
1328 my $sql_statement= "SELECT * FROM $known_server_tn";
1329 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1330 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1331 my $host_name = $hit->{hostname};
1332 my $host_key = $hit->{hostkey};
1333 $answer =~ s/<target>KNOWN_SERVER<\/target>/<target>$host_name<\/target>/g;
1334 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1335 &update_jobdb_status_for_send_msgs($answer, $error);
1336 }
1337 }
1339 # target of msg is GOsa
1340 elsif( $answer_target eq "GOSA" ) {
1341 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1342 my $add_on = "";
1343 if( defined $session_id ) {
1344 $add_on = ".session_id=$session_id";
1345 }
1346 # answer is for GOSA and has to returned to connected client
1347 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1348 $client_answer = $gosa_answer.$add_on;
1349 }
1351 # target of msg is job queue at this host
1352 elsif( $answer_target eq "JOBDB") {
1353 $answer =~ /<header>(\S+)<\/header>/;
1354 my $header;
1355 if( defined $1 ) { $header = $1; }
1356 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1357 &update_jobdb_status_for_send_msgs($answer, $error);
1358 }
1360 # target of msg is a mac address
1361 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 ) {
1362 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1363 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1364 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1365 my $found_ip_flag = 0;
1366 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1367 my $host_name = $hit->{hostname};
1368 my $host_key = $hit->{hostkey};
1369 $answer =~ s/$answer_target/$host_name/g;
1370 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1371 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1372 &update_jobdb_status_for_send_msgs($answer, $error);
1373 $found_ip_flag++ ;
1374 }
1375 if( $found_ip_flag == 0) {
1376 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1377 if( $bus_activ eq "true" ) {
1378 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1379 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1380 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1381 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1382 my $bus_address = $hit->{hostname};
1383 my $bus_key = $hit->{hostkey};
1384 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1385 &update_jobdb_status_for_send_msgs($answer, $error);
1386 last;
1387 }
1388 }
1390 }
1392 # answer is for one specific host
1393 } else {
1394 # get encrypt_key
1395 my $encrypt_key = &get_encrypt_key($answer_target);
1396 if( not defined $encrypt_key ) {
1397 # unknown target, forward msg to bus
1398 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1399 if( $bus_activ eq "true" ) {
1400 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1401 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1402 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1403 my $res_length = keys( %{$query_res} );
1404 if( $res_length == 0 ){
1405 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1406 "no bus found in known_server", 3);
1407 }
1408 else {
1409 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1410 my $bus_key = $hit->{hostkey};
1411 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1412 &update_jobdb_status_for_send_msgs($answer, $error);
1413 }
1414 }
1415 }
1416 next;
1417 }
1418 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1419 &update_jobdb_status_for_send_msgs($answer, $error);
1420 }
1421 }
1422 }
1423 }
1425 my $filter = POE::Filter::Reference->new();
1426 my %result = (
1427 status => "seems ok to me",
1428 answer => $client_answer,
1429 );
1431 my $output = $filter->put( [ \%result ] );
1432 print @$output;
1435 }
1437 sub session_start {
1438 my ($kernel) = $_[KERNEL];
1439 &trigger_db_loop($kernel);
1440 $global_kernel = $kernel;
1441 $kernel->yield('register_at_foreign_servers');
1442 $kernel->yield('create_fai_server_db', $fai_server_tn );
1443 $kernel->yield('create_fai_release_db', $fai_release_tn );
1444 $kernel->yield('watch_for_next_tasks');
1445 $kernel->sig(USR1 => "sig_handler");
1446 $kernel->sig(USR2 => "create_packages_list_db");
1447 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1448 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1449 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1450 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1451 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1452 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1454 }
1456 sub trigger_db_loop {
1457 my ($kernel) = @_ ;
1458 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1459 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1460 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1461 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1462 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1463 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1464 }
1467 sub watch_for_done_jobs {
1468 my ($kernel,$heap) = @_[KERNEL, HEAP];
1470 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1471 my $res = $job_db->select_dbentry( $sql_statement );
1473 while( my ($id, $hit) = each %{$res} ) {
1474 my $jobdb_id = $hit->{id};
1475 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1476 my $res = $job_db->del_dbentry($sql_statement);
1477 }
1479 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1480 }
1483 sub watch_for_new_jobs {
1484 if($watch_for_new_jobs_in_progress == 0) {
1485 $watch_for_new_jobs_in_progress = 1;
1486 my ($kernel,$heap) = @_[KERNEL, HEAP];
1488 # check gosa job queue for jobs with executable timestamp
1489 my $timestamp = &get_time();
1490 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1491 my $res = $job_db->exec_statement( $sql_statement );
1493 # Merge all new jobs that would do the same actions
1494 my @drops;
1495 my $hits;
1496 foreach my $hit (reverse @{$res} ) {
1497 my $macaddress= lc @{$hit}[8];
1498 my $headertag= @{$hit}[5];
1499 if(
1500 defined($hits->{$macaddress}) &&
1501 defined($hits->{$macaddress}->{$headertag}) &&
1502 defined($hits->{$macaddress}->{$headertag}[0])
1503 ) {
1504 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1505 }
1506 $hits->{$macaddress}->{$headertag}= $hit;
1507 }
1509 # Delete new jobs with a matching job in state 'processing'
1510 foreach my $macaddress (keys %{$hits}) {
1511 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1512 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1513 if(defined($jobdb_id)) {
1514 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1515 my $res = $job_db->exec_statement( $sql_statement );
1516 foreach my $hit (@{$res}) {
1517 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1518 }
1519 } else {
1520 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1521 }
1522 }
1523 }
1525 # Commit deletion
1526 $job_db->exec_statementlist(\@drops);
1528 # Look for new jobs that could be executed
1529 foreach my $macaddress (keys %{$hits}) {
1531 # Look if there is an executing job
1532 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1533 my $res = $job_db->exec_statement( $sql_statement );
1535 # Skip new jobs for host if there is a processing job
1536 if(defined($res) and defined @{$res}[0]) {
1537 next;
1538 }
1540 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1541 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1542 if(defined($jobdb_id)) {
1543 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1545 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1546 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1547 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1549 # expect macaddress is unique!!!!!!
1550 my $target = $res_hash->{1}->{hostname};
1552 # change header
1553 $job_msg =~ s/<header>job_/<header>gosa_/;
1555 # add sqlite_id
1556 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1558 $job_msg =~ /<header>(\S+)<\/header>/;
1559 my $header = $1 ;
1560 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1562 # update status in job queue to 'processing'
1563 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1564 my $res = $job_db->update_dbentry($sql_statement);
1565 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1567 # We don't want parallel processing
1568 last;
1569 }
1570 }
1571 }
1573 $watch_for_new_jobs_in_progress = 0;
1574 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1575 }
1576 }
1579 sub watch_for_new_messages {
1580 my ($kernel,$heap) = @_[KERNEL, HEAP];
1581 my @coll_user_msg; # collection list of outgoing messages
1583 # check messaging_db for new incoming messages with executable timestamp
1584 my $timestamp = &get_time();
1585 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1586 my $res = $messaging_db->exec_statement( $sql_statement );
1587 foreach my $hit (@{$res}) {
1589 # create outgoing messages
1590 my $message_to = @{$hit}[3];
1591 # translate message_to to plain login name
1592 my @message_to_l = split(/,/, $message_to);
1593 my %receiver_h;
1594 foreach my $receiver (@message_to_l) {
1595 if ($receiver =~ /^u_([\s\S]*)$/) {
1596 $receiver_h{$1} = 0;
1597 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1598 my $group_name = $1;
1599 # fetch all group members from ldap and add them to receiver hash
1600 my $ldap_handle = &get_ldap_handle();
1601 if (defined $ldap_handle) {
1602 my $mesg = $ldap_handle->search(
1603 base => $ldap_base,
1604 scope => 'sub',
1605 attrs => ['memberUid'],
1606 filter => "cn=$group_name",
1607 );
1608 if ($mesg->count) {
1609 my @entries = $mesg->entries;
1610 foreach my $entry (@entries) {
1611 my @receivers= $entry->get_value("memberUid");
1612 foreach my $receiver (@receivers) {
1613 $receiver_h{$1} = 0;
1614 }
1615 }
1616 }
1617 # translating errors ?
1618 if ($mesg->code) {
1619 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1620 }
1621 # ldap handle error ?
1622 } else {
1623 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1624 }
1625 } else {
1626 my $sbjct = &encode_base64(@{$hit}[1]);
1627 my $msg = &encode_base64(@{$hit}[7]);
1628 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1629 }
1630 }
1631 my @receiver_l = keys(%receiver_h);
1633 my $message_id = @{$hit}[0];
1635 #add each outgoing msg to messaging_db
1636 my $receiver;
1637 foreach $receiver (@receiver_l) {
1638 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1639 "VALUES ('".
1640 $message_id."', '". # id
1641 @{$hit}[1]."', '". # subject
1642 @{$hit}[2]."', '". # message_from
1643 $receiver."', '". # message_to
1644 "none"."', '". # flag
1645 "out"."', '". # direction
1646 @{$hit}[6]."', '". # delivery_time
1647 @{$hit}[7]."', '". # message
1648 $timestamp."'". # timestamp
1649 ")";
1650 &daemon_log("M DEBUG: $sql_statement", 1);
1651 my $res = $messaging_db->exec_statement($sql_statement);
1652 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1653 }
1655 # set incoming message to flag d=deliverd
1656 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1657 &daemon_log("M DEBUG: $sql_statement", 7);
1658 $res = $messaging_db->update_dbentry($sql_statement);
1659 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1660 }
1662 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1663 return;
1664 }
1666 sub watch_for_delivery_messages {
1667 my ($kernel, $heap) = @_[KERNEL, HEAP];
1669 # select outgoing messages
1670 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1671 #&daemon_log("0 DEBUG: $sql", 7);
1672 my $res = $messaging_db->exec_statement( $sql_statement );
1674 # build out msg for each usr
1675 foreach my $hit (@{$res}) {
1676 my $receiver = @{$hit}[3];
1677 my $msg_id = @{$hit}[0];
1678 my $subject = @{$hit}[1];
1679 my $message = @{$hit}[7];
1681 # resolve usr -> host where usr is logged in
1682 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1683 #&daemon_log("0 DEBUG: $sql", 7);
1684 my $res = $login_users_db->exec_statement($sql);
1686 # reciver is logged in nowhere
1687 if (not ref(@$res[0]) eq "ARRAY") { next; }
1689 my $send_succeed = 0;
1690 foreach my $hit (@$res) {
1691 my $receiver_host = @$hit[0];
1692 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1694 # fetch key to encrypt msg propperly for usr/host
1695 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1696 &daemon_log("0 DEBUG: $sql", 7);
1697 my $res = $known_clients_db->select_dbentry($sql);
1699 # host is already down
1700 if (not ref(@$res[0]) eq "ARRAY") { next; }
1702 # host is on
1703 my $receiver_key = @{@{$res}[0]}[2];
1704 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1705 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1706 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1707 if ($error == 0 ) {
1708 $send_succeed++ ;
1709 }
1710 }
1712 if ($send_succeed) {
1713 # set outgoing msg at db to deliverd
1714 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1715 &daemon_log("0 DEBUG: $sql", 7);
1716 my $res = $messaging_db->exec_statement($sql);
1717 }
1718 }
1720 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1721 return;
1722 }
1725 sub watch_for_done_messages {
1726 my ($kernel,$heap) = @_[KERNEL, HEAP];
1728 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1729 #&daemon_log("0 DEBUG: $sql", 7);
1730 my $res = $messaging_db->exec_statement($sql);
1732 foreach my $hit (@{$res}) {
1733 my $msg_id = @{$hit}[0];
1735 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1736 #&daemon_log("0 DEBUG: $sql", 7);
1737 my $res = $messaging_db->exec_statement($sql);
1739 # not all usr msgs have been seen till now
1740 if ( ref(@$res[0]) eq "ARRAY") { next; }
1742 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1743 #&daemon_log("0 DEBUG: $sql", 7);
1744 $res = $messaging_db->exec_statement($sql);
1746 }
1748 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1749 return;
1750 }
1753 sub watch_for_old_known_clients {
1754 my ($kernel,$heap) = @_[KERNEL, HEAP];
1756 my $sql_statement = "SELECT * FROM $known_clients_tn";
1757 my $res = $known_clients_db->select_dbentry( $sql_statement );
1759 my $act_time = int(&get_time());
1761 while ( my ($hit_num, $hit) = each %$res) {
1762 my $expired_timestamp = int($hit->{'timestamp'});
1763 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1764 my $dt = DateTime->new( year => $1,
1765 month => $2,
1766 day => $3,
1767 hour => $4,
1768 minute => $5,
1769 second => $6,
1770 );
1772 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1773 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1774 if ($act_time > $expired_timestamp) {
1775 my $hostname = $hit->{'hostname'};
1776 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1777 my $del_res = $known_clients_db->exec_statement($del_sql);
1779 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1780 }
1782 }
1784 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1785 }
1788 sub watch_for_next_tasks {
1789 my ($kernel,$heap) = @_[KERNEL, HEAP];
1791 my $sql = "SELECT * FROM $incoming_tn";
1792 my $res = $incoming_db->select_dbentry($sql);
1794 while ( my ($hit_num, $hit) = each %$res) {
1795 my $headertag = $hit->{'headertag'};
1796 if ($headertag =~ /^answer_(\d+)/) {
1797 # do not start processing, this message is for a still running POE::Wheel
1798 next;
1799 }
1800 my $message_id = $hit->{'id'};
1801 $kernel->yield('next_task', $hit);
1803 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1804 my $res = $incoming_db->exec_statement($sql);
1805 }
1807 $kernel->delay_set('watch_for_next_tasks', 1);
1808 }
1811 sub get_ldap_handle {
1812 my ($session_id) = @_;
1813 my $heap;
1814 my $ldap_handle;
1816 if (not defined $session_id ) { $session_id = 0 };
1817 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1819 if ($session_id == 0) {
1820 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1821 $ldap_handle = Net::LDAP->new( $ldap_uri );
1822 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password);
1824 } else {
1825 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1826 if( defined $session_reference ) {
1827 $heap = $session_reference->get_heap();
1828 }
1830 if (not defined $heap) {
1831 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1832 return;
1833 }
1835 # TODO: This "if" is nonsense, because it doesn't prove that the
1836 # used handle is still valid - or if we've to reconnect...
1837 #if (not exists $heap->{ldap_handle}) {
1838 $ldap_handle = Net::LDAP->new( $ldap_uri );
1839 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1840 $heap->{ldap_handle} = $ldap_handle;
1841 #}
1842 }
1843 return $ldap_handle;
1844 }
1847 sub change_fai_state {
1848 my ($st, $targets, $session_id) = @_;
1849 $session_id = 0 if not defined $session_id;
1850 # Set FAI state to localboot
1851 my %mapActions= (
1852 reboot => '',
1853 update => 'softupdate',
1854 localboot => 'localboot',
1855 reinstall => 'install',
1856 rescan => '',
1857 wake => '',
1858 memcheck => 'memcheck',
1859 sysinfo => 'sysinfo',
1860 install => 'install',
1861 );
1863 # Return if this is unknown
1864 if (!exists $mapActions{ $st }){
1865 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1866 return;
1867 }
1869 my $state= $mapActions{ $st };
1871 my $ldap_handle = &get_ldap_handle($session_id);
1872 if( defined($ldap_handle) ) {
1874 # Build search filter for hosts
1875 my $search= "(&(objectClass=GOhard)";
1876 foreach (@{$targets}){
1877 $search.= "(macAddress=$_)";
1878 }
1879 $search.= ")";
1881 # If there's any host inside of the search string, procress them
1882 if (!($search =~ /macAddress/)){
1883 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1884 return;
1885 }
1887 # Perform search for Unit Tag
1888 my $mesg = $ldap_handle->search(
1889 base => $ldap_base,
1890 scope => 'sub',
1891 attrs => ['dn', 'FAIstate', 'objectClass'],
1892 filter => "$search"
1893 );
1895 if ($mesg->count) {
1896 my @entries = $mesg->entries;
1897 foreach my $entry (@entries) {
1898 # Only modify entry if it is not set to '$state'
1899 if ($entry->get_value("FAIstate") ne "$state"){
1900 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1901 my $result;
1902 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1903 if (exists $tmp{'FAIobject'}){
1904 if ($state eq ''){
1905 $result= $ldap_handle->modify($entry->dn, changes => [
1906 delete => [ FAIstate => [] ] ]);
1907 } else {
1908 $result= $ldap_handle->modify($entry->dn, changes => [
1909 replace => [ FAIstate => $state ] ]);
1910 }
1911 } elsif ($state ne ''){
1912 $result= $ldap_handle->modify($entry->dn, changes => [
1913 add => [ objectClass => 'FAIobject' ],
1914 add => [ FAIstate => $state ] ]);
1915 }
1917 # Errors?
1918 if ($result->code){
1919 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1920 }
1921 } else {
1922 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1923 }
1924 }
1925 }
1926 # if no ldap handle defined
1927 } else {
1928 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1929 }
1931 }
1934 sub change_goto_state {
1935 my ($st, $targets, $session_id) = @_;
1936 $session_id = 0 if not defined $session_id;
1938 # Switch on or off?
1939 my $state= $st eq 'active' ? 'active': 'locked';
1941 my $ldap_handle = &get_ldap_handle($session_id);
1942 if( defined($ldap_handle) ) {
1944 # Build search filter for hosts
1945 my $search= "(&(objectClass=GOhard)";
1946 foreach (@{$targets}){
1947 $search.= "(macAddress=$_)";
1948 }
1949 $search.= ")";
1951 # If there's any host inside of the search string, procress them
1952 if (!($search =~ /macAddress/)){
1953 return;
1954 }
1956 # Perform search for Unit Tag
1957 my $mesg = $ldap_handle->search(
1958 base => $ldap_base,
1959 scope => 'sub',
1960 attrs => ['dn', 'gotoMode'],
1961 filter => "$search"
1962 );
1964 if ($mesg->count) {
1965 my @entries = $mesg->entries;
1966 foreach my $entry (@entries) {
1968 # Only modify entry if it is not set to '$state'
1969 if ($entry->get_value("gotoMode") ne $state){
1971 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1972 my $result;
1973 $result= $ldap_handle->modify($entry->dn, changes => [
1974 replace => [ gotoMode => $state ] ]);
1976 # Errors?
1977 if ($result->code){
1978 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1979 }
1981 }
1982 }
1983 }
1985 }
1986 }
1989 sub run_create_fai_server_db {
1990 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1991 my $session_id = $session->ID;
1992 my $task = POE::Wheel::Run->new(
1993 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1994 StdoutEvent => "session_run_result",
1995 StderrEvent => "session_run_debug",
1996 CloseEvent => "session_run_done",
1997 );
1999 $heap->{task}->{ $task->ID } = $task;
2000 return;
2001 }
2004 sub create_fai_server_db {
2005 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2006 my $result;
2008 if (not defined $session_id) { $session_id = 0; }
2009 my $ldap_handle = &get_ldap_handle();
2010 if(defined($ldap_handle)) {
2011 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2012 my $mesg= $ldap_handle->search(
2013 base => $ldap_base,
2014 scope => 'sub',
2015 attrs => ['FAIrepository', 'gosaUnitTag'],
2016 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2017 );
2018 if($mesg->{'resultCode'} == 0 &&
2019 $mesg->count != 0) {
2020 foreach my $entry (@{$mesg->{entries}}) {
2021 if($entry->exists('FAIrepository')) {
2022 # Add an entry for each Repository configured for server
2023 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2024 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2025 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2026 $result= $fai_server_db->add_dbentry( {
2027 table => $table_name,
2028 primkey => ['server', 'release', 'tag'],
2029 server => $tmp_url,
2030 release => $tmp_release,
2031 sections => $tmp_sections,
2032 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2033 } );
2034 }
2035 }
2036 }
2037 }
2038 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2040 # TODO: Find a way to post the 'create_packages_list_db' event
2041 if(not defined($dont_create_packages_list)) {
2042 &create_packages_list_db(undef, undef, $session_id);
2043 }
2044 }
2046 $ldap_handle->disconnect;
2047 return $result;
2048 }
2051 sub run_create_fai_release_db {
2052 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2053 my $session_id = $session->ID;
2054 my $task = POE::Wheel::Run->new(
2055 Program => sub { &create_fai_release_db($table_name, $session_id) },
2056 StdoutEvent => "session_run_result",
2057 StderrEvent => "session_run_debug",
2058 CloseEvent => "session_run_done",
2059 );
2061 $heap->{task}->{ $task->ID } = $task;
2062 return;
2063 }
2066 sub create_fai_release_db {
2067 my ($table_name, $session_id) = @_;
2068 my $result;
2070 # used for logging
2071 if (not defined $session_id) { $session_id = 0; }
2073 my $ldap_handle = &get_ldap_handle();
2074 if(defined($ldap_handle)) {
2075 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2076 my $mesg= $ldap_handle->search(
2077 base => $ldap_base,
2078 scope => 'sub',
2079 attrs => [],
2080 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2081 );
2082 if($mesg->{'resultCode'} == 0 &&
2083 $mesg->count != 0) {
2084 # Walk through all possible FAI container ou's
2085 my @sql_list;
2086 my $timestamp= &get_time();
2087 foreach my $ou (@{$mesg->{entries}}) {
2088 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2089 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2090 my @tmp_array=get_fai_release_entries($tmp_classes);
2091 if(@tmp_array) {
2092 foreach my $entry (@tmp_array) {
2093 if(defined($entry) && ref($entry) eq 'HASH') {
2094 my $sql=
2095 "INSERT INTO $table_name "
2096 ."(timestamp, release, class, type, state) VALUES ("
2097 .$timestamp.","
2098 ."'".$entry->{'release'}."',"
2099 ."'".$entry->{'class'}."',"
2100 ."'".$entry->{'type'}."',"
2101 ."'".$entry->{'state'}."')";
2102 push @sql_list, $sql;
2103 }
2104 }
2105 }
2106 }
2107 }
2109 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2110 if(@sql_list) {
2111 unshift @sql_list, "VACUUM";
2112 unshift @sql_list, "DELETE FROM $table_name";
2113 $fai_release_db->exec_statementlist(\@sql_list);
2114 }
2115 daemon_log("$session_id DEBUG: Done with inserting",7);
2116 }
2117 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2118 }
2119 $ldap_handle->disconnect;
2120 return $result;
2121 }
2123 sub get_fai_types {
2124 my $tmp_classes = shift || return undef;
2125 my @result;
2127 foreach my $type(keys %{$tmp_classes}) {
2128 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2129 my $entry = {
2130 type => $type,
2131 state => $tmp_classes->{$type}[0],
2132 };
2133 push @result, $entry;
2134 }
2135 }
2137 return @result;
2138 }
2140 sub get_fai_state {
2141 my $result = "";
2142 my $tmp_classes = shift || return $result;
2144 foreach my $type(keys %{$tmp_classes}) {
2145 if(defined($tmp_classes->{$type}[0])) {
2146 $result = $tmp_classes->{$type}[0];
2148 # State is equal for all types in class
2149 last;
2150 }
2151 }
2153 return $result;
2154 }
2156 sub resolve_fai_classes {
2157 my ($fai_base, $ldap_handle, $session_id) = @_;
2158 if (not defined $session_id) { $session_id = 0; }
2159 my $result;
2160 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2161 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2162 my $fai_classes;
2164 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2165 my $mesg= $ldap_handle->search(
2166 base => $fai_base,
2167 scope => 'sub',
2168 attrs => ['cn','objectClass','FAIstate'],
2169 filter => $fai_filter,
2170 );
2171 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2173 if($mesg->{'resultCode'} == 0 &&
2174 $mesg->count != 0) {
2175 foreach my $entry (@{$mesg->{entries}}) {
2176 if($entry->exists('cn')) {
2177 my $tmp_dn= $entry->dn();
2179 # Skip classname and ou dn parts for class
2180 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2182 # Skip classes without releases
2183 if((!defined($tmp_release)) || length($tmp_release)==0) {
2184 next;
2185 }
2187 my $tmp_cn= $entry->get_value('cn');
2188 my $tmp_state= $entry->get_value('FAIstate');
2190 my $tmp_type;
2191 # Get FAI type
2192 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2193 if(grep $_ eq $oclass, @possible_fai_classes) {
2194 $tmp_type= $oclass;
2195 last;
2196 }
2197 }
2199 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2200 # A Subrelease
2201 my @sub_releases = split(/,/, $tmp_release);
2203 # Walk through subreleases and build hash tree
2204 my $hash;
2205 while(my $tmp_sub_release = pop @sub_releases) {
2206 $hash .= "\{'$tmp_sub_release'\}->";
2207 }
2208 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2209 } else {
2210 # A branch, no subrelease
2211 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2212 }
2213 } elsif (!$entry->exists('cn')) {
2214 my $tmp_dn= $entry->dn();
2215 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2217 # Skip classes without releases
2218 if((!defined($tmp_release)) || length($tmp_release)==0) {
2219 next;
2220 }
2222 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2223 # A Subrelease
2224 my @sub_releases= split(/,/, $tmp_release);
2226 # Walk through subreleases and build hash tree
2227 my $hash;
2228 while(my $tmp_sub_release = pop @sub_releases) {
2229 $hash .= "\{'$tmp_sub_release'\}->";
2230 }
2231 # Remove the last two characters
2232 chop($hash);
2233 chop($hash);
2235 eval('$fai_classes->'.$hash.'= {}');
2236 } else {
2237 # A branch, no subrelease
2238 if(!exists($fai_classes->{$tmp_release})) {
2239 $fai_classes->{$tmp_release} = {};
2240 }
2241 }
2242 }
2243 }
2245 # The hash is complete, now we can honor the copy-on-write based missing entries
2246 foreach my $release (keys %$fai_classes) {
2247 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2248 }
2249 }
2250 return $result;
2251 }
2253 sub apply_fai_inheritance {
2254 my $fai_classes = shift || return {};
2255 my $tmp_classes;
2257 # Get the classes from the branch
2258 foreach my $class (keys %{$fai_classes}) {
2259 # Skip subreleases
2260 if($class =~ /^ou=.*$/) {
2261 next;
2262 } else {
2263 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2264 }
2265 }
2267 # Apply to each subrelease
2268 foreach my $subrelease (keys %{$fai_classes}) {
2269 if($subrelease =~ /ou=/) {
2270 foreach my $tmp_class (keys %{$tmp_classes}) {
2271 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2272 $fai_classes->{$subrelease}->{$tmp_class} =
2273 deep_copy($tmp_classes->{$tmp_class});
2274 } else {
2275 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2276 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2277 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2278 deep_copy($tmp_classes->{$tmp_class}->{$type});
2279 }
2280 }
2281 }
2282 }
2283 }
2284 }
2286 # Find subreleases in deeper levels
2287 foreach my $subrelease (keys %{$fai_classes}) {
2288 if($subrelease =~ /ou=/) {
2289 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2290 if($subsubrelease =~ /ou=/) {
2291 apply_fai_inheritance($fai_classes->{$subrelease});
2292 }
2293 }
2294 }
2295 }
2297 return $fai_classes;
2298 }
2300 sub get_fai_release_entries {
2301 my $tmp_classes = shift || return;
2302 my $parent = shift || "";
2303 my @result = shift || ();
2305 foreach my $entry (keys %{$tmp_classes}) {
2306 if(defined($entry)) {
2307 if($entry =~ /^ou=.*$/) {
2308 my $release_name = $entry;
2309 $release_name =~ s/ou=//g;
2310 if(length($parent)>0) {
2311 $release_name = $parent."/".$release_name;
2312 }
2313 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2314 foreach my $bufentry(@bufentries) {
2315 push @result, $bufentry;
2316 }
2317 } else {
2318 my @types = get_fai_types($tmp_classes->{$entry});
2319 foreach my $type (@types) {
2320 push @result,
2321 {
2322 'class' => $entry,
2323 'type' => $type->{'type'},
2324 'release' => $parent,
2325 'state' => $type->{'state'},
2326 };
2327 }
2328 }
2329 }
2330 }
2332 return @result;
2333 }
2335 sub deep_copy {
2336 my $this = shift;
2337 if (not ref $this) {
2338 $this;
2339 } elsif (ref $this eq "ARRAY") {
2340 [map deep_copy($_), @$this];
2341 } elsif (ref $this eq "HASH") {
2342 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2343 } else { die "what type is $_?" }
2344 }
2347 sub session_run_result {
2348 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2349 $kernel->sig(CHLD => "child_reap");
2350 }
2352 sub session_run_debug {
2353 my $result = $_[ARG0];
2354 print STDERR "$result\n";
2355 }
2357 sub session_run_done {
2358 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2359 delete $heap->{task}->{$task_id};
2360 }
2363 sub create_sources_list {
2364 my $session_id = shift;
2365 my $ldap_handle = &main::get_ldap_handle;
2366 my $result="/tmp/gosa_si_tmp_sources_list";
2368 # Remove old file
2369 if(stat($result)) {
2370 unlink($result);
2371 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2372 }
2374 my $fh;
2375 open($fh, ">$result");
2376 if (not defined $fh) {
2377 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2378 return undef;
2379 }
2380 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2381 my $mesg=$ldap_handle->search(
2382 base => $main::ldap_server_dn,
2383 scope => 'base',
2384 attrs => 'FAIrepository',
2385 filter => 'objectClass=FAIrepositoryServer'
2386 );
2387 if($mesg->count) {
2388 foreach my $entry(@{$mesg->{'entries'}}) {
2389 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2390 my ($server, $tag, $release, $sections)= split /\|/, $value;
2391 my $line = "deb $server $release";
2392 $sections =~ s/,/ /g;
2393 $line.= " $sections";
2394 print $fh $line."\n";
2395 }
2396 }
2397 }
2398 } else {
2399 if (defined $main::ldap_server_dn){
2400 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2401 } else {
2402 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2403 }
2404 }
2405 close($fh);
2407 return $result;
2408 }
2411 sub run_create_packages_list_db {
2412 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2413 my $session_id = $session->ID;
2415 my $task = POE::Wheel::Run->new(
2416 Priority => +20,
2417 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2418 StdoutEvent => "session_run_result",
2419 StderrEvent => "session_run_debug",
2420 CloseEvent => "session_run_done",
2421 );
2422 $heap->{task}->{ $task->ID } = $task;
2423 }
2426 sub create_packages_list_db {
2427 my ($ldap_handle, $sources_file, $session_id) = @_;
2429 # it should not be possible to trigger a recreation of packages_list_db
2430 # while packages_list_db is under construction, so set flag packages_list_under_construction
2431 # which is tested befor recreation can be started
2432 if (-r $packages_list_under_construction) {
2433 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2434 return;
2435 } else {
2436 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2437 # set packages_list_under_construction to true
2438 system("touch $packages_list_under_construction");
2439 @packages_list_statements=();
2440 }
2442 if (not defined $session_id) { $session_id = 0; }
2443 if (not defined $ldap_handle) {
2444 $ldap_handle= &get_ldap_handle();
2446 if (not defined $ldap_handle) {
2447 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2448 unlink($packages_list_under_construction);
2449 return;
2450 }
2451 }
2452 if (not defined $sources_file) {
2453 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2454 $sources_file = &create_sources_list($session_id);
2455 }
2457 if (not defined $sources_file) {
2458 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2459 unlink($packages_list_under_construction);
2460 return;
2461 }
2463 my $line;
2465 open(CONFIG, "<$sources_file") or do {
2466 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2467 unlink($packages_list_under_construction);
2468 return;
2469 };
2471 # Read lines
2472 while ($line = <CONFIG>){
2473 # Unify
2474 chop($line);
2475 $line =~ s/^\s+//;
2476 $line =~ s/^\s+/ /;
2478 # Strip comments
2479 $line =~ s/#.*$//g;
2481 # Skip empty lines
2482 if ($line =~ /^\s*$/){
2483 next;
2484 }
2486 # Interpret deb line
2487 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2488 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2489 my $section;
2490 foreach $section (split(' ', $sections)){
2491 &parse_package_info( $baseurl, $dist, $section, $session_id );
2492 }
2493 }
2494 }
2496 close (CONFIG);
2498 find(\&cleanup_and_extract, keys( %repo_dirs ));
2499 &main::strip_packages_list_statements();
2500 unshift @packages_list_statements, "VACUUM";
2501 $packages_list_db->exec_statementlist(\@packages_list_statements);
2502 unlink($packages_list_under_construction);
2503 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2504 return;
2505 }
2507 # This function should do some intensive task to minimize the db-traffic
2508 sub strip_packages_list_statements {
2509 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2510 my @new_statement_list=();
2511 my $hash;
2512 my $insert_hash;
2513 my $update_hash;
2514 my $delete_hash;
2515 my $local_timestamp=get_time();
2517 foreach my $existing_entry (@existing_entries) {
2518 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2519 }
2521 foreach my $statement (@packages_list_statements) {
2522 if($statement =~ /^INSERT/i) {
2523 # Assign the values from the insert statement
2524 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2525 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2526 if(exists($hash->{$distribution}->{$package}->{$version})) {
2527 # If section or description has changed, update the DB
2528 if(
2529 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2530 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2531 ) {
2532 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2533 }
2534 } else {
2535 # Insert a non-existing entry to db
2536 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2537 }
2538 } elsif ($statement =~ /^UPDATE/i) {
2539 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2540 /^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;
2541 foreach my $distribution (keys %{$hash}) {
2542 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2543 # update the insertion hash to execute only one query per package (insert instead insert+update)
2544 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2545 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2546 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2547 my $section;
2548 my $description;
2549 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2550 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2551 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2552 }
2553 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2554 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2555 }
2556 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2557 }
2558 }
2559 }
2560 }
2561 }
2563 # TODO: Check for orphaned entries
2565 # unroll the insert_hash
2566 foreach my $distribution (keys %{$insert_hash}) {
2567 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2568 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2569 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2570 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2571 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2572 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2573 ."'$local_timestamp')";
2574 }
2575 }
2576 }
2578 # unroll the update hash
2579 foreach my $distribution (keys %{$update_hash}) {
2580 foreach my $package (keys %{$update_hash->{$distribution}}) {
2581 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2582 my $set = "";
2583 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2584 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2585 }
2586 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2587 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2588 }
2589 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2590 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2591 }
2592 if(defined($set) and length($set) > 0) {
2593 $set .= "timestamp = '$local_timestamp'";
2594 } else {
2595 next;
2596 }
2597 push @new_statement_list,
2598 "UPDATE $main::packages_list_tn SET $set WHERE"
2599 ." distribution = '$distribution'"
2600 ." AND package = '$package'"
2601 ." AND version = '$version'";
2602 }
2603 }
2604 }
2606 @packages_list_statements = @new_statement_list;
2607 }
2610 sub parse_package_info {
2611 my ($baseurl, $dist, $section, $session_id)= @_;
2612 my ($package);
2613 if (not defined $session_id) { $session_id = 0; }
2614 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2615 $repo_dirs{ "${repo_path}/pool" } = 1;
2617 foreach $package ("Packages.gz"){
2618 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2619 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2620 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2621 }
2623 }
2626 sub get_package {
2627 my ($url, $dest, $session_id)= @_;
2628 if (not defined $session_id) { $session_id = 0; }
2630 my $tpath = dirname($dest);
2631 -d "$tpath" || mkpath "$tpath";
2633 # This is ugly, but I've no time to take a look at "how it works in perl"
2634 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2635 system("gunzip -cd '$dest' > '$dest.in'");
2636 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2637 unlink($dest);
2638 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2639 } else {
2640 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2641 }
2642 return 0;
2643 }
2646 sub parse_package {
2647 my ($path, $dist, $srv_path, $session_id)= @_;
2648 if (not defined $session_id) { $session_id = 0;}
2649 my ($package, $version, $section, $description);
2650 my $PACKAGES;
2651 my $timestamp = &get_time();
2653 if(not stat("$path.in")) {
2654 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2655 return;
2656 }
2658 open($PACKAGES, "<$path.in");
2659 if(not defined($PACKAGES)) {
2660 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2661 return;
2662 }
2664 # Read lines
2665 while (<$PACKAGES>){
2666 my $line = $_;
2667 # Unify
2668 chop($line);
2670 # Use empty lines as a trigger
2671 if ($line =~ /^\s*$/){
2672 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2673 push(@packages_list_statements, $sql);
2674 $package = "none";
2675 $version = "none";
2676 $section = "none";
2677 $description = "none";
2678 next;
2679 }
2681 # Trigger for package name
2682 if ($line =~ /^Package:\s/){
2683 ($package)= ($line =~ /^Package: (.*)$/);
2684 next;
2685 }
2687 # Trigger for version
2688 if ($line =~ /^Version:\s/){
2689 ($version)= ($line =~ /^Version: (.*)$/);
2690 next;
2691 }
2693 # Trigger for description
2694 if ($line =~ /^Description:\s/){
2695 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2696 next;
2697 }
2699 # Trigger for section
2700 if ($line =~ /^Section:\s/){
2701 ($section)= ($line =~ /^Section: (.*)$/);
2702 next;
2703 }
2705 # Trigger for filename
2706 if ($line =~ /^Filename:\s/){
2707 my ($filename) = ($line =~ /^Filename: (.*)$/);
2708 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2709 next;
2710 }
2711 }
2713 close( $PACKAGES );
2714 unlink( "$path.in" );
2715 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2716 }
2719 sub store_fileinfo {
2720 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2722 my %fileinfo = (
2723 'package' => $package,
2724 'dist' => $dist,
2725 'version' => $vers,
2726 );
2728 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2729 }
2732 sub cleanup_and_extract {
2733 my $fileinfo = $repo_files{ $File::Find::name };
2735 if( defined $fileinfo ) {
2737 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2738 my $sql;
2739 my $package = $fileinfo->{ 'package' };
2740 my $newver = $fileinfo->{ 'version' };
2742 mkpath($dir);
2743 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2745 if( -f "$dir/DEBIAN/templates" ) {
2747 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2749 my $tmpl= "";
2750 {
2751 local $/=undef;
2752 open FILE, "$dir/DEBIAN/templates";
2753 $tmpl = &encode_base64(<FILE>);
2754 close FILE;
2755 }
2756 rmtree("$dir/DEBIAN/templates");
2758 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2759 push @packages_list_statements, $sql;
2760 }
2761 }
2763 return;
2764 }
2767 sub register_at_foreign_servers {
2768 my ($kernel) = $_[KERNEL];
2770 # hole alle bekannten server aus known_server_db
2771 my $server_sql = "SELECT * FROM $known_server_tn";
2772 my $server_res = $known_server_db->exec_statement($server_sql);
2774 # no entries in known_server_db
2775 if (not ref(@$server_res[0]) eq "ARRAY") {
2776 # TODO
2777 }
2779 # detect already connected clients
2780 my $client_sql = "SELECT * FROM $known_clients_tn";
2781 my $client_res = $known_clients_db->exec_statement($client_sql);
2783 # send my server details to all other gosa-si-server within the network
2784 foreach my $hit (@$server_res) {
2785 my $hostname = @$hit[0];
2786 my $hostkey = &create_passwd;
2788 # add already connected clients to registration message
2789 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2790 &add_content2xml_hash($myhash, 'key', $hostkey);
2791 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2793 # build registration message and send it
2794 my $foreign_server_msg = &create_xml_string($myhash);
2795 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2796 }
2798 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2799 return;
2800 }
2803 #==== MAIN = main ==============================================================
2804 # parse commandline options
2805 Getopt::Long::Configure( "bundling" );
2806 GetOptions("h|help" => \&usage,
2807 "c|config=s" => \$cfg_file,
2808 "f|foreground" => \$foreground,
2809 "v|verbose+" => \$verbose,
2810 "no-bus+" => \$no_bus,
2811 "no-arp+" => \$no_arp,
2812 );
2814 # read and set config parameters
2815 &check_cmdline_param ;
2816 &read_configfile;
2817 &check_pid;
2819 $SIG{CHLD} = 'IGNORE';
2821 # forward error messages to logfile
2822 if( ! $foreground ) {
2823 open( STDIN, '+>/dev/null' );
2824 open( STDOUT, '+>&STDIN' );
2825 open( STDERR, '+>&STDIN' );
2826 }
2828 # Just fork, if we are not in foreground mode
2829 if( ! $foreground ) {
2830 chdir '/' or die "Can't chdir to /: $!";
2831 $pid = fork;
2832 setsid or die "Can't start a new session: $!";
2833 umask 0;
2834 } else {
2835 $pid = $$;
2836 }
2838 # Do something useful - put our PID into the pid_file
2839 if( 0 != $pid ) {
2840 open( LOCK_FILE, ">$pid_file" );
2841 print LOCK_FILE "$pid\n";
2842 close( LOCK_FILE );
2843 if( !$foreground ) {
2844 exit( 0 )
2845 };
2846 }
2848 # parse head url and revision from svn
2849 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2850 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2851 $server_headURL = defined $1 ? $1 : 'unknown' ;
2852 $server_revision = defined $2 ? $2 : 'unknown' ;
2853 if ($server_headURL =~ /\/tag\// ||
2854 $server_headURL =~ /\/branches\// ) {
2855 $server_status = "stable";
2856 } else {
2857 $server_status = "developmental" ;
2858 }
2861 daemon_log(" ", 1);
2862 daemon_log("$0 started!", 1);
2863 daemon_log("status: $server_status", 1);
2864 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2866 if ($no_bus > 0) {
2867 $bus_activ = "false"
2868 }
2870 # connect to incoming_db
2871 unlink($incoming_file_name);
2872 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2873 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2875 # connect to gosa-si job queue
2876 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2877 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2879 # connect to known_clients_db
2880 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2881 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2883 # connect to foreign_clients_db
2884 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2885 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2887 # connect to known_server_db
2888 unlink($known_server_file_name);
2889 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2890 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2892 # connect to login_usr_db
2893 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2894 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2896 # connect to fai_server_db and fai_release_db
2897 unlink($fai_server_file_name);
2898 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2899 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2901 unlink($fai_release_file_name);
2902 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2903 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2905 # connect to packages_list_db
2906 #unlink($packages_list_file_name);
2907 unlink($packages_list_under_construction);
2908 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2909 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2911 # connect to messaging_db
2912 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2913 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2916 # create xml object used for en/decrypting
2917 $xml = new XML::Simple();
2920 # foreign servers
2921 my @foreign_server_list;
2923 # add foreign server from cfg file
2924 if ($foreign_server_string ne "") {
2925 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2926 foreach my $foreign_server (@cfg_foreign_server_list) {
2927 push(@foreign_server_list, $foreign_server);
2928 }
2929 }
2931 # add foreign server from dns
2932 my @tmp_servers;
2933 if ( !$server_domain) {
2934 # Try our DNS Searchlist
2935 for my $domain(get_dns_domains()) {
2936 chomp($domain);
2937 my @tmp_domains= &get_server_addresses($domain);
2938 if(@tmp_domains) {
2939 for my $tmp_server(@tmp_domains) {
2940 push @tmp_servers, $tmp_server;
2941 }
2942 }
2943 }
2944 if(@tmp_servers && length(@tmp_servers)==0) {
2945 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2946 }
2947 } else {
2948 @tmp_servers = &get_server_addresses($server_domain);
2949 if( 0 == @tmp_servers ) {
2950 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2951 }
2952 }
2953 foreach my $server (@tmp_servers) {
2954 unshift(@foreign_server_list, $server);
2955 }
2956 # eliminate duplicate entries
2957 @foreign_server_list = &del_doubles(@foreign_server_list);
2958 my $all_foreign_server = join(", ", @foreign_server_list);
2959 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2961 # add all found foreign servers to known_server
2962 my $act_timestamp = &get_time();
2963 foreach my $foreign_server (@foreign_server_list) {
2964 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2965 primkey=>['hostname'],
2966 hostname=>$foreign_server,
2967 status=>'not_jet_registered',
2968 hostkey=>"none",
2969 timestamp=>$act_timestamp,
2970 } );
2971 }
2974 POE::Component::Server::TCP->new(
2975 Alias => "TCP_SERVER",
2976 Port => $server_port,
2977 ClientInput => sub {
2978 my ($kernel, $input) = @_[KERNEL, ARG0];
2979 push(@tasks, $input);
2980 push(@msgs_to_decrypt, $input);
2981 $kernel->yield("msg_to_decrypt");
2982 },
2983 InlineStates => {
2984 msg_to_decrypt => \&msg_to_decrypt,
2985 next_task => \&next_task,
2986 task_result => \&handle_task_result,
2987 task_done => \&handle_task_done,
2988 task_debug => \&handle_task_debug,
2989 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2990 }
2991 );
2993 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2995 # create session for repeatedly checking the job queue for jobs
2996 POE::Session->create(
2997 inline_states => {
2998 _start => \&session_start,
2999 register_at_foreign_servers => \®ister_at_foreign_servers,
3000 sig_handler => \&sig_handler,
3001 next_task => \&next_task,
3002 task_result => \&handle_task_result,
3003 task_done => \&handle_task_done,
3004 task_debug => \&handle_task_debug,
3005 watch_for_next_tasks => \&watch_for_next_tasks,
3006 watch_for_new_messages => \&watch_for_new_messages,
3007 watch_for_delivery_messages => \&watch_for_delivery_messages,
3008 watch_for_done_messages => \&watch_for_done_messages,
3009 watch_for_new_jobs => \&watch_for_new_jobs,
3010 watch_for_done_jobs => \&watch_for_done_jobs,
3011 watch_for_old_known_clients => \&watch_for_old_known_clients,
3012 create_packages_list_db => \&run_create_packages_list_db,
3013 create_fai_server_db => \&run_create_fai_server_db,
3014 create_fai_release_db => \&run_create_fai_release_db,
3015 session_run_result => \&session_run_result,
3016 session_run_debug => \&session_run_debug,
3017 session_run_done => \&session_run_done,
3018 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3019 }
3020 );
3023 # import all modules
3024 &import_modules;
3026 # TODO
3027 # check wether all modules are gosa-si valid passwd check
3031 POE::Kernel->run();
3032 exit;