7a9ca4756e8c2a8b3cf55250dd4190e21ebe039d
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 -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 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
823 #=== FUNCTION ================================================================
824 # NAME: get_ip
825 # PARAMETERS: interface name (i.e. eth0)
826 # RETURNS: (ip address)
827 # DESCRIPTION: Uses ioctl to get ip address directly from system.
828 #===============================================================================
829 #sub get_ip {
830 # my $ifreq= shift;
831 # my $result= "";
832 # my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
833 # my $proto= getprotobyname('ip');
834 #
835 # socket SOCKET, PF_INET, SOCK_DGRAM, $proto
836 # or die "socket: $!";
837 #
838 # if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
839 # my ($if, $sin) = unpack 'a16 a16', $ifreq;
840 # my ($port, $addr) = sockaddr_in $sin;
841 # my $ip = inet_ntoa $addr;
842 #
843 # if ($ip && length($ip) > 0) {
844 # $result = $ip;
845 # }
846 # }
847 #
848 # return $result;
849 #}
852 sub get_local_ip_for_remote_ip {
853 my $remote_ip= shift;
854 my $result="0.0.0.0";
856 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
857 if($remote_ip eq "127.0.0.1") {
858 $result = "127.0.0.1";
859 } else {
860 my $PROC_NET_ROUTE= ('/proc/net/route');
862 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
863 or die "Could not open $PROC_NET_ROUTE";
865 my @ifs = <PROC_NET_ROUTE>;
867 close(PROC_NET_ROUTE);
869 # Eat header line
870 shift @ifs;
871 chomp @ifs;
872 foreach my $line(@ifs) {
873 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
874 my $destination;
875 my $mask;
876 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
877 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
879 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
880 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
881 # destination matches route, save mac and exit
882 $result= &get_ip($Iface);
883 last;
884 }
885 }
886 }
887 } else {
888 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
889 }
890 return $result;
891 }
894 sub send_msg_to_target {
895 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
896 my $error = 0;
897 my $header;
898 my $timestamp = &get_time();
899 my $new_status;
900 my $act_status;
901 my ($sql_statement, $res);
903 if( $msg_header ) {
904 $header = "'$msg_header'-";
905 } else {
906 $header = "";
907 }
909 # Patch the source ip
910 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
911 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
912 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
913 }
915 # encrypt xml msg
916 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
918 # opensocket
919 my $socket = &open_socket($address);
920 if( !$socket ) {
921 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
922 $error++;
923 }
925 if( $error == 0 ) {
926 # send xml msg
927 print $socket $crypted_msg."\n";
929 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
930 daemon_log("$session_id DEBUG: message:\n$msg", 9);
932 }
934 # close socket in any case
935 if( $socket ) {
936 close $socket;
937 }
939 if( $error > 0 ) { $new_status = "down"; }
940 else { $new_status = $msg_header; }
943 # known_clients
944 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
945 $res = $known_clients_db->select_dbentry($sql_statement);
946 if( keys(%$res) == 1) {
947 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
948 if ($act_status eq "down" && $new_status eq "down") {
949 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
950 $res = $known_clients_db->del_dbentry($sql_statement);
951 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
952 } else {
953 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
954 $res = $known_clients_db->update_dbentry($sql_statement);
955 if($new_status eq "down"){
956 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957 } else {
958 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
959 }
960 }
961 }
963 # known_server
964 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
965 $res = $known_server_db->select_dbentry($sql_statement);
966 if( keys(%$res) == 1) {
967 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
968 if ($act_status eq "down" && $new_status eq "down") {
969 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
970 $res = $known_server_db->del_dbentry($sql_statement);
971 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
972 }
973 else {
974 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
975 $res = $known_server_db->update_dbentry($sql_statement);
976 if($new_status eq "down"){
977 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
978 } else {
979 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
980 }
981 }
982 }
983 return $error;
984 }
987 sub update_jobdb_status_for_send_msgs {
988 my ($answer, $error) = @_;
989 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
990 my $jobdb_id = $1;
992 # sending msg faild
993 if( $error ) {
994 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
995 my $sql_statement = "UPDATE $job_queue_tn ".
996 "SET status='error', result='can not deliver msg, please consult log file' ".
997 "WHERE id=$jobdb_id";
998 my $res = $job_db->update_dbentry($sql_statement);
999 }
1001 # sending msg was successful
1002 } else {
1003 my $sql_statement = "UPDATE $job_queue_tn ".
1004 "SET status='done' ".
1005 "WHERE id=$jobdb_id AND status='processed'";
1006 my $res = $job_db->update_dbentry($sql_statement);
1007 }
1008 }
1009 }
1012 sub sig_handler {
1013 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1014 daemon_log("0 INFO got signal '$signal'", 1);
1015 $kernel->sig_handled();
1016 return;
1017 }
1020 sub msg_to_decrypt {
1021 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1022 my $session_id = $session->ID;
1023 my ($msg, $msg_hash, $module);
1024 my $error = 0;
1026 # hole neue msg aus @msgs_to_decrypt
1027 my $next_msg = shift @msgs_to_decrypt;
1029 # entschlüssle sie
1031 # msg is from a new client or gosa
1032 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1033 # msg is from a gosa-si-server or gosa-si-bus
1034 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1035 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1036 }
1037 # msg is from a gosa-si-client
1038 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1039 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1040 }
1041 # an error occurred
1042 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1043 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1044 # could not understand a msg from its server the client cause a re-registering process
1045 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1046 "' to cause a re-registering of the client if necessary", 5);
1047 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1048 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1049 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1050 my $host_name = $hit->{'hostname'};
1051 my $host_key = $hit->{'hostkey'};
1052 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1053 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1054 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1055 }
1056 $error++;
1057 }
1060 my $header;
1061 my $target;
1062 my $source;
1063 my $done = 0;
1064 my $sql;
1065 my $res;
1066 # check whether this message should be processed here
1067 if ($error == 0) {
1068 $header = @{$msg_hash->{'header'}}[0];
1069 $target = @{$msg_hash->{'target'}}[0];
1070 $source = @{$msg_hash->{'source'}}[0];
1071 my ($target_ip, $target_port) = split(':', $target);
1072 if ($target ne "GOSA") {
1073 my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1074 }
1076 # target and source is equal to GOSA -> process here
1077 if (not $done) {
1078 if ($target eq "GOSA" && $source eq "GOSA") {
1079 $done = 1;
1080 }
1081 }
1083 # target is own address without forward_to_gosa-tag -> process here
1084 if (not $done) {
1085 if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1086 $done = 1;
1087 if ($source eq "GOSA") {
1088 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1089 }
1090 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1091 }
1092 }
1094 # target is a client address in known_clients -> process here
1095 if (not $done) {
1096 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1097 $res = $known_clients_db->select_dbentry($sql);
1098 if (keys(%$res) > 0) {
1099 $done = 1;
1100 my $hostname = $res->{1}->{'hostname'};
1101 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1102 print STDERR "target is a client address in known_clients -> process here\n";
1103 }
1104 }
1106 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1107 if (not $done) {
1108 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1109 my $gosa_at;
1110 my $gosa_session_id;
1111 if (($target eq $server_address) && (defined $forward_to_gosa)){
1112 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1113 if ($gosa_at ne $server_address) {
1114 $done = 1;
1115 print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1116 }
1117 }
1118 }
1120 # if message should be processed here -> add message to incoming_db
1121 if ($done) {
1123 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1124 # so gosa-si-server knows how to process this kind of messages
1125 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1126 $module = "GosaPackages";
1127 }
1129 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1130 primkey=>[],
1131 headertag=>$header,
1132 targettag=>$target,
1133 xmlmessage=>$msg,
1134 timestamp=>&get_time,
1135 module=>$module,
1136 sessionid=>$session_id,
1137 } );
1139 }
1141 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1142 if (not $done) {
1143 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1144 my $gosa_at;
1145 my $gosa_session_id;
1146 if (($target eq $server_address) && (defined $forward_to_gosa)){
1147 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1148 if ($gosa_at eq $server_address) {
1149 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1150 if( defined $session_reference ) {
1151 $heap = $session_reference->get_heap();
1152 }
1153 if(exists $heap->{'client'}) {
1154 $msg = &encrypt_msg($msg, $GosaPackages_key);
1155 $heap->{'client'}->put($msg);
1156 }
1157 $done = 1;
1158 print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1159 }
1160 }
1162 }
1164 # target is a client address in foreign_clients -> forward to registration server
1165 if (not $done) {
1166 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1167 $res = $foreign_clients_db->select_dbentry($sql);
1168 if (keys(%$res) > 0) {
1169 my $hostname = $res->{1}->{'hostname'};
1170 my $regserver = $res->{1}->{'regserver'};
1171 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1172 my $res = $known_server_db->select_dbentry($sql);
1173 if (keys(%$res) > 0) {
1174 my $regserver_key = $res->{1}->{'hostkey'};
1175 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1176 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1177 if ($source eq "GOSA") {
1178 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1179 }
1180 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1181 }
1182 $done = 1;
1183 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1184 }
1185 }
1187 # target is a server address -> forward to server
1188 if (not $done) {
1189 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1190 $res = $known_server_db->select_dbentry($sql);
1191 if (keys(%$res) > 0) {
1192 my $hostkey = $res->{1}->{'hostkey'};
1194 if ($source eq "GOSA") {
1195 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1196 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1198 }
1200 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1201 $done = 1;
1202 print STDERR "target is a server address -> forward to server\n";
1203 }
1206 }
1208 if (not $done) {
1209 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1210 }
1211 }
1213 return;
1214 }
1217 sub next_task {
1218 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1219 my $running_task = POE::Wheel::Run->new(
1220 Program => sub { process_task($session, $heap, $task) },
1221 StdioFilter => POE::Filter::Reference->new(),
1222 StdoutEvent => "task_result",
1223 StderrEvent => "task_debug",
1224 CloseEvent => "task_done",
1225 );
1226 $heap->{task}->{ $running_task->ID } = $running_task;
1227 }
1229 sub handle_task_result {
1230 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1231 my $client_answer = $result->{'answer'};
1232 if( $client_answer =~ s/session_id=(\d+)$// ) {
1233 my $session_id = $1;
1234 if( defined $session_id ) {
1235 my $session_reference = $kernel->ID_id_to_session($session_id);
1236 if( defined $session_reference ) {
1237 $heap = $session_reference->get_heap();
1238 }
1239 }
1241 if(exists $heap->{'client'}) {
1242 $heap->{'client'}->put($client_answer);
1243 }
1244 }
1245 $kernel->sig(CHLD => "child_reap");
1246 }
1248 sub handle_task_debug {
1249 my $result = $_[ARG0];
1250 print STDERR "$result\n";
1251 }
1253 sub handle_task_done {
1254 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1255 delete $heap->{task}->{$task_id};
1256 }
1258 sub process_task {
1259 no strict "refs";
1260 my ($session, $heap, $task) = @_;
1261 my $error = 0;
1262 my $answer_l;
1263 my ($answer_header, @answer_target_l, $answer_source);
1264 my $client_answer = "";
1266 # prepare all variables needed to process message
1267 my $msg = $task->{'xmlmessage'};
1268 my $incoming_id = $task->{'id'};
1269 my $module = $task->{'module'};
1270 my $header = $task->{'headertag'};
1271 my $session_id = $task->{'sessionid'};
1272 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1273 my $source = @{$msg_hash->{'source'}}[0];
1275 # set timestamp of incoming client uptodate, so client will not
1276 # be deleted from known_clients because of expiration
1277 my $act_time = &get_time();
1278 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1279 my $res = $known_clients_db->exec_statement($sql);
1281 ######################
1282 # process incoming msg
1283 if( $error == 0) {
1284 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1285 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1286 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1288 if ( 0 < @{$answer_l} ) {
1289 my $answer_str = join("\n", @{$answer_l});
1290 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1291 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1292 }
1293 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1294 } else {
1295 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1296 }
1298 }
1299 if( !$answer_l ) { $error++ };
1301 ########
1302 # answer
1303 if( $error == 0 ) {
1305 foreach my $answer ( @{$answer_l} ) {
1306 # check outgoing msg to xml validity
1307 my $answer_hash = &check_outgoing_xml_validity($answer);
1308 if( not defined $answer_hash ) { next; }
1310 $answer_header = @{$answer_hash->{'header'}}[0];
1311 @answer_target_l = @{$answer_hash->{'target'}};
1312 $answer_source = @{$answer_hash->{'source'}}[0];
1314 # deliver msg to all targets
1315 foreach my $answer_target ( @answer_target_l ) {
1317 # targets of msg are all gosa-si-clients in known_clients_db
1318 if( $answer_target eq "*" ) {
1319 # answer is for all clients
1320 my $sql_statement= "SELECT * FROM known_clients";
1321 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1322 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1323 my $host_name = $hit->{hostname};
1324 my $host_key = $hit->{hostkey};
1325 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1326 &update_jobdb_status_for_send_msgs($answer, $error);
1327 }
1328 }
1330 # targets of msg are all gosa-si-server in known_server_db
1331 elsif( $answer_target eq "KNOWN_SERVER" ) {
1332 # answer is for all server in known_server
1333 my $sql_statement= "SELECT * FROM $known_server_tn";
1334 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1335 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1336 my $host_name = $hit->{hostname};
1337 my $host_key = $hit->{hostkey};
1338 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1339 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1340 &update_jobdb_status_for_send_msgs($answer, $error);
1341 }
1342 }
1344 # target of msg is GOsa
1345 elsif( $answer_target eq "GOSA" ) {
1346 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1347 my $add_on = "";
1348 if( defined $session_id ) {
1349 $add_on = ".session_id=$session_id";
1350 }
1351 # answer is for GOSA and has to returned to connected client
1352 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1353 $client_answer = $gosa_answer.$add_on;
1354 }
1356 # target of msg is job queue at this host
1357 elsif( $answer_target eq "JOBDB") {
1358 $answer =~ /<header>(\S+)<\/header>/;
1359 my $header;
1360 if( defined $1 ) { $header = $1; }
1361 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1362 &update_jobdb_status_for_send_msgs($answer, $error);
1363 }
1365 # target of msg is a mac address
1366 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 ) {
1367 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1368 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1369 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1370 my $found_ip_flag = 0;
1371 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1372 my $host_name = $hit->{hostname};
1373 my $host_key = $hit->{hostkey};
1374 $answer =~ s/$answer_target/$host_name/g;
1375 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1376 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1377 &update_jobdb_status_for_send_msgs($answer, $error);
1378 $found_ip_flag++ ;
1379 }
1380 if( $found_ip_flag == 0) {
1381 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1382 if( $bus_activ eq "true" ) {
1383 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1384 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1385 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1386 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1387 my $bus_address = $hit->{hostname};
1388 my $bus_key = $hit->{hostkey};
1389 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1390 &update_jobdb_status_for_send_msgs($answer, $error);
1391 last;
1392 }
1393 }
1395 }
1397 # answer is for one specific host
1398 } else {
1399 # get encrypt_key
1400 my $encrypt_key = &get_encrypt_key($answer_target);
1401 if( not defined $encrypt_key ) {
1402 # unknown target, forward msg to bus
1403 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1404 if( $bus_activ eq "true" ) {
1405 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1406 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1407 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1408 my $res_length = keys( %{$query_res} );
1409 if( $res_length == 0 ){
1410 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1411 "no bus found in known_server", 3);
1412 }
1413 else {
1414 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1415 my $bus_key = $hit->{hostkey};
1416 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1417 &update_jobdb_status_for_send_msgs($answer, $error);
1418 }
1419 }
1420 }
1421 next;
1422 }
1423 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1424 &update_jobdb_status_for_send_msgs($answer, $error);
1425 }
1426 }
1427 }
1428 }
1430 my $filter = POE::Filter::Reference->new();
1431 my %result = (
1432 status => "seems ok to me",
1433 answer => $client_answer,
1434 );
1436 my $output = $filter->put( [ \%result ] );
1437 print @$output;
1440 }
1442 sub session_start {
1443 my ($kernel) = $_[KERNEL];
1444 &trigger_db_loop($kernel);
1445 $global_kernel = $kernel;
1446 $kernel->yield('register_at_foreign_servers');
1447 $kernel->yield('create_fai_server_db', $fai_server_tn );
1448 $kernel->yield('create_fai_release_db', $fai_release_tn );
1449 $kernel->yield('watch_for_next_tasks');
1450 $kernel->sig(USR1 => "sig_handler");
1451 $kernel->sig(USR2 => "create_packages_list_db");
1452 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1453 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1454 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1455 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1456 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1457 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1459 }
1461 sub trigger_db_loop {
1462 my ($kernel) = @_ ;
1463 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1464 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1465 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1466 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1467 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1468 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1469 }
1472 sub watch_for_done_jobs {
1473 my ($kernel,$heap) = @_[KERNEL, HEAP];
1475 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1476 my $res = $job_db->select_dbentry( $sql_statement );
1478 while( my ($id, $hit) = each %{$res} ) {
1479 my $jobdb_id = $hit->{id};
1480 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1481 my $res = $job_db->del_dbentry($sql_statement);
1482 }
1484 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1485 }
1488 sub watch_for_new_jobs {
1489 if($watch_for_new_jobs_in_progress == 0) {
1490 $watch_for_new_jobs_in_progress = 1;
1491 my ($kernel,$heap) = @_[KERNEL, HEAP];
1493 # check gosa job queue for jobs with executable timestamp
1494 my $timestamp = &get_time();
1495 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1496 my $res = $job_db->exec_statement( $sql_statement );
1498 # Merge all new jobs that would do the same actions
1499 my @drops;
1500 my $hits;
1501 foreach my $hit (reverse @{$res} ) {
1502 my $macaddress= lc @{$hit}[8];
1503 my $headertag= @{$hit}[5];
1504 if(
1505 defined($hits->{$macaddress}) &&
1506 defined($hits->{$macaddress}->{$headertag}) &&
1507 defined($hits->{$macaddress}->{$headertag}[0])
1508 ) {
1509 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1510 }
1511 $hits->{$macaddress}->{$headertag}= $hit;
1512 }
1514 # Delete new jobs with a matching job in state 'processing'
1515 foreach my $macaddress (keys %{$hits}) {
1516 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1517 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1518 if(defined($jobdb_id)) {
1519 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1520 my $res = $job_db->exec_statement( $sql_statement );
1521 foreach my $hit (@{$res}) {
1522 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1523 }
1524 } else {
1525 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1526 }
1527 }
1528 }
1530 # Commit deletion
1531 $job_db->exec_statementlist(\@drops);
1533 # Look for new jobs that could be executed
1534 foreach my $macaddress (keys %{$hits}) {
1536 # Look if there is an executing job
1537 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1538 my $res = $job_db->exec_statement( $sql_statement );
1540 # Skip new jobs for host if there is a processing job
1541 if(defined($res) and defined @{$res}[0]) {
1542 next;
1543 }
1545 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1546 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1547 if(defined($jobdb_id)) {
1548 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1550 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1551 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1552 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1554 # expect macaddress is unique!!!!!!
1555 my $target = $res_hash->{1}->{hostname};
1557 # change header
1558 $job_msg =~ s/<header>job_/<header>gosa_/;
1560 # add sqlite_id
1561 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1563 $job_msg =~ /<header>(\S+)<\/header>/;
1564 my $header = $1 ;
1565 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1567 # update status in job queue to 'processing'
1568 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1569 my $res = $job_db->update_dbentry($sql_statement);
1570 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1572 # We don't want parallel processing
1573 last;
1574 }
1575 }
1576 }
1578 $watch_for_new_jobs_in_progress = 0;
1579 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1580 }
1581 }
1584 sub watch_for_new_messages {
1585 my ($kernel,$heap) = @_[KERNEL, HEAP];
1586 my @coll_user_msg; # collection list of outgoing messages
1588 # check messaging_db for new incoming messages with executable timestamp
1589 my $timestamp = &get_time();
1590 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1591 my $res = $messaging_db->exec_statement( $sql_statement );
1592 foreach my $hit (@{$res}) {
1594 # create outgoing messages
1595 my $message_to = @{$hit}[3];
1596 # translate message_to to plain login name
1597 my @message_to_l = split(/,/, $message_to);
1598 my %receiver_h;
1599 foreach my $receiver (@message_to_l) {
1600 if ($receiver =~ /^u_([\s\S]*)$/) {
1601 $receiver_h{$1} = 0;
1602 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1603 my $group_name = $1;
1604 # fetch all group members from ldap and add them to receiver hash
1605 my $ldap_handle = &get_ldap_handle();
1606 if (defined $ldap_handle) {
1607 my $mesg = $ldap_handle->search(
1608 base => $ldap_base,
1609 scope => 'sub',
1610 attrs => ['memberUid'],
1611 filter => "cn=$group_name",
1612 );
1613 if ($mesg->count) {
1614 my @entries = $mesg->entries;
1615 foreach my $entry (@entries) {
1616 my @receivers= $entry->get_value("memberUid");
1617 foreach my $receiver (@receivers) {
1618 $receiver_h{$1} = 0;
1619 }
1620 }
1621 }
1622 # translating errors ?
1623 if ($mesg->code) {
1624 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1625 }
1626 # ldap handle error ?
1627 } else {
1628 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1629 }
1630 } else {
1631 my $sbjct = &encode_base64(@{$hit}[1]);
1632 my $msg = &encode_base64(@{$hit}[7]);
1633 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1634 }
1635 }
1636 my @receiver_l = keys(%receiver_h);
1638 my $message_id = @{$hit}[0];
1640 #add each outgoing msg to messaging_db
1641 my $receiver;
1642 foreach $receiver (@receiver_l) {
1643 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1644 "VALUES ('".
1645 $message_id."', '". # id
1646 @{$hit}[1]."', '". # subject
1647 @{$hit}[2]."', '". # message_from
1648 $receiver."', '". # message_to
1649 "none"."', '". # flag
1650 "out"."', '". # direction
1651 @{$hit}[6]."', '". # delivery_time
1652 @{$hit}[7]."', '". # message
1653 $timestamp."'". # timestamp
1654 ")";
1655 &daemon_log("M DEBUG: $sql_statement", 1);
1656 my $res = $messaging_db->exec_statement($sql_statement);
1657 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1658 }
1660 # set incoming message to flag d=deliverd
1661 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1662 &daemon_log("M DEBUG: $sql_statement", 7);
1663 $res = $messaging_db->update_dbentry($sql_statement);
1664 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1665 }
1667 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1668 return;
1669 }
1671 sub watch_for_delivery_messages {
1672 my ($kernel, $heap) = @_[KERNEL, HEAP];
1674 # select outgoing messages
1675 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1676 #&daemon_log("0 DEBUG: $sql", 7);
1677 my $res = $messaging_db->exec_statement( $sql_statement );
1679 # build out msg for each usr
1680 foreach my $hit (@{$res}) {
1681 my $receiver = @{$hit}[3];
1682 my $msg_id = @{$hit}[0];
1683 my $subject = @{$hit}[1];
1684 my $message = @{$hit}[7];
1686 # resolve usr -> host where usr is logged in
1687 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1688 #&daemon_log("0 DEBUG: $sql", 7);
1689 my $res = $login_users_db->exec_statement($sql);
1691 # reciver is logged in nowhere
1692 if (not ref(@$res[0]) eq "ARRAY") { next; }
1694 my $send_succeed = 0;
1695 foreach my $hit (@$res) {
1696 my $receiver_host = @$hit[0];
1697 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1699 # fetch key to encrypt msg propperly for usr/host
1700 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1701 &daemon_log("0 DEBUG: $sql", 7);
1702 my $res = $known_clients_db->select_dbentry($sql);
1704 # host is already down
1705 if (not ref(@$res[0]) eq "ARRAY") { next; }
1707 # host is on
1708 my $receiver_key = @{@{$res}[0]}[2];
1709 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1710 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1711 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1712 if ($error == 0 ) {
1713 $send_succeed++ ;
1714 }
1715 }
1717 if ($send_succeed) {
1718 # set outgoing msg at db to deliverd
1719 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1720 &daemon_log("0 DEBUG: $sql", 7);
1721 my $res = $messaging_db->exec_statement($sql);
1722 }
1723 }
1725 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1726 return;
1727 }
1730 sub watch_for_done_messages {
1731 my ($kernel,$heap) = @_[KERNEL, HEAP];
1733 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1734 #&daemon_log("0 DEBUG: $sql", 7);
1735 my $res = $messaging_db->exec_statement($sql);
1737 foreach my $hit (@{$res}) {
1738 my $msg_id = @{$hit}[0];
1740 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1741 #&daemon_log("0 DEBUG: $sql", 7);
1742 my $res = $messaging_db->exec_statement($sql);
1744 # not all usr msgs have been seen till now
1745 if ( ref(@$res[0]) eq "ARRAY") { next; }
1747 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1748 #&daemon_log("0 DEBUG: $sql", 7);
1749 $res = $messaging_db->exec_statement($sql);
1751 }
1753 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1754 return;
1755 }
1758 sub watch_for_old_known_clients {
1759 my ($kernel,$heap) = @_[KERNEL, HEAP];
1761 my $sql_statement = "SELECT * FROM $known_clients_tn";
1762 my $res = $known_clients_db->select_dbentry( $sql_statement );
1764 my $act_time = int(&get_time());
1766 while ( my ($hit_num, $hit) = each %$res) {
1767 my $expired_timestamp = int($hit->{'timestamp'});
1768 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1769 my $dt = DateTime->new( year => $1,
1770 month => $2,
1771 day => $3,
1772 hour => $4,
1773 minute => $5,
1774 second => $6,
1775 );
1777 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1778 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1779 if ($act_time > $expired_timestamp) {
1780 my $hostname = $hit->{'hostname'};
1781 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1782 my $del_res = $known_clients_db->exec_statement($del_sql);
1784 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1785 }
1787 }
1789 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1790 }
1793 sub watch_for_next_tasks {
1794 my ($kernel,$heap) = @_[KERNEL, HEAP];
1796 my $sql = "SELECT * FROM $incoming_tn";
1797 my $res = $incoming_db->select_dbentry($sql);
1799 while ( my ($hit_num, $hit) = each %$res) {
1800 my $headertag = $hit->{'headertag'};
1801 if ($headertag =~ /^answer_(\d+)/) {
1802 # do not start processing, this message is for a still running POE::Wheel
1803 next;
1804 }
1805 my $message_id = $hit->{'id'};
1806 $kernel->yield('next_task', $hit);
1808 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1809 my $res = $incoming_db->exec_statement($sql);
1810 }
1812 $kernel->delay_set('watch_for_next_tasks', 1);
1813 }
1816 sub get_ldap_handle {
1817 my ($session_id) = @_;
1818 my $heap;
1819 my $ldap_handle;
1821 if (not defined $session_id ) { $session_id = 0 };
1822 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1824 if ($session_id == 0) {
1825 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1826 $ldap_handle = Net::LDAP->new( $ldap_uri );
1827 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1829 } else {
1830 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1831 if( defined $session_reference ) {
1832 $heap = $session_reference->get_heap();
1833 }
1835 if (not defined $heap) {
1836 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1837 return;
1838 }
1840 # TODO: This "if" is nonsense, because it doesn't prove that the
1841 # used handle is still valid - or if we've to reconnect...
1842 #if (not exists $heap->{ldap_handle}) {
1843 $ldap_handle = Net::LDAP->new( $ldap_uri );
1844 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1845 $heap->{ldap_handle} = $ldap_handle;
1846 #}
1847 }
1848 return $ldap_handle;
1849 }
1852 sub change_fai_state {
1853 my ($st, $targets, $session_id) = @_;
1854 $session_id = 0 if not defined $session_id;
1855 # Set FAI state to localboot
1856 my %mapActions= (
1857 reboot => '',
1858 update => 'softupdate',
1859 localboot => 'localboot',
1860 reinstall => 'install',
1861 rescan => '',
1862 wake => '',
1863 memcheck => 'memcheck',
1864 sysinfo => 'sysinfo',
1865 install => 'install',
1866 );
1868 # Return if this is unknown
1869 if (!exists $mapActions{ $st }){
1870 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1871 return;
1872 }
1874 my $state= $mapActions{ $st };
1876 my $ldap_handle = &get_ldap_handle($session_id);
1877 if( defined($ldap_handle) ) {
1879 # Build search filter for hosts
1880 my $search= "(&(objectClass=GOhard)";
1881 foreach (@{$targets}){
1882 $search.= "(macAddress=$_)";
1883 }
1884 $search.= ")";
1886 # If there's any host inside of the search string, procress them
1887 if (!($search =~ /macAddress/)){
1888 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1889 return;
1890 }
1892 # Perform search for Unit Tag
1893 my $mesg = $ldap_handle->search(
1894 base => $ldap_base,
1895 scope => 'sub',
1896 attrs => ['dn', 'FAIstate', 'objectClass'],
1897 filter => "$search"
1898 );
1900 if ($mesg->count) {
1901 my @entries = $mesg->entries;
1902 foreach my $entry (@entries) {
1903 # Only modify entry if it is not set to '$state'
1904 if ($entry->get_value("FAIstate") ne "$state"){
1905 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1906 my $result;
1907 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1908 if (exists $tmp{'FAIobject'}){
1909 if ($state eq ''){
1910 $result= $ldap_handle->modify($entry->dn, changes => [
1911 delete => [ FAIstate => [] ] ]);
1912 } else {
1913 $result= $ldap_handle->modify($entry->dn, changes => [
1914 replace => [ FAIstate => $state ] ]);
1915 }
1916 } elsif ($state ne ''){
1917 $result= $ldap_handle->modify($entry->dn, changes => [
1918 add => [ objectClass => 'FAIobject' ],
1919 add => [ FAIstate => $state ] ]);
1920 }
1922 # Errors?
1923 if ($result->code){
1924 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1925 }
1926 } else {
1927 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1928 }
1929 }
1930 }
1931 # if no ldap handle defined
1932 } else {
1933 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1934 }
1936 }
1939 sub change_goto_state {
1940 my ($st, $targets, $session_id) = @_;
1941 $session_id = 0 if not defined $session_id;
1943 # Switch on or off?
1944 my $state= $st eq 'active' ? 'active': 'locked';
1946 my $ldap_handle = &get_ldap_handle($session_id);
1947 if( defined($ldap_handle) ) {
1949 # Build search filter for hosts
1950 my $search= "(&(objectClass=GOhard)";
1951 foreach (@{$targets}){
1952 $search.= "(macAddress=$_)";
1953 }
1954 $search.= ")";
1956 # If there's any host inside of the search string, procress them
1957 if (!($search =~ /macAddress/)){
1958 return;
1959 }
1961 # Perform search for Unit Tag
1962 my $mesg = $ldap_handle->search(
1963 base => $ldap_base,
1964 scope => 'sub',
1965 attrs => ['dn', 'gotoMode'],
1966 filter => "$search"
1967 );
1969 if ($mesg->count) {
1970 my @entries = $mesg->entries;
1971 foreach my $entry (@entries) {
1973 # Only modify entry if it is not set to '$state'
1974 if ($entry->get_value("gotoMode") ne $state){
1976 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1977 my $result;
1978 $result= $ldap_handle->modify($entry->dn, changes => [
1979 replace => [ gotoMode => $state ] ]);
1981 # Errors?
1982 if ($result->code){
1983 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1984 }
1986 }
1987 }
1988 }
1990 }
1991 }
1994 sub run_create_fai_server_db {
1995 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1996 my $session_id = $session->ID;
1997 my $task = POE::Wheel::Run->new(
1998 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1999 StdoutEvent => "session_run_result",
2000 StderrEvent => "session_run_debug",
2001 CloseEvent => "session_run_done",
2002 );
2004 $heap->{task}->{ $task->ID } = $task;
2005 return;
2006 }
2009 sub create_fai_server_db {
2010 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2011 my $result;
2013 if (not defined $session_id) { $session_id = 0; }
2014 my $ldap_handle = &get_ldap_handle();
2015 if(defined($ldap_handle)) {
2016 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2017 my $mesg= $ldap_handle->search(
2018 base => $ldap_base,
2019 scope => 'sub',
2020 attrs => ['FAIrepository', 'gosaUnitTag'],
2021 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2022 );
2023 if($mesg->{'resultCode'} == 0 &&
2024 $mesg->count != 0) {
2025 foreach my $entry (@{$mesg->{entries}}) {
2026 if($entry->exists('FAIrepository')) {
2027 # Add an entry for each Repository configured for server
2028 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2029 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2030 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2031 $result= $fai_server_db->add_dbentry( {
2032 table => $table_name,
2033 primkey => ['server', 'release', 'tag'],
2034 server => $tmp_url,
2035 release => $tmp_release,
2036 sections => $tmp_sections,
2037 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2038 } );
2039 }
2040 }
2041 }
2042 }
2043 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2045 # TODO: Find a way to post the 'create_packages_list_db' event
2046 if(not defined($dont_create_packages_list)) {
2047 &create_packages_list_db(undef, undef, $session_id);
2048 }
2049 }
2051 $ldap_handle->disconnect;
2052 return $result;
2053 }
2056 sub run_create_fai_release_db {
2057 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2058 my $session_id = $session->ID;
2059 my $task = POE::Wheel::Run->new(
2060 Program => sub { &create_fai_release_db($table_name, $session_id) },
2061 StdoutEvent => "session_run_result",
2062 StderrEvent => "session_run_debug",
2063 CloseEvent => "session_run_done",
2064 );
2066 $heap->{task}->{ $task->ID } = $task;
2067 return;
2068 }
2071 sub create_fai_release_db {
2072 my ($table_name, $session_id) = @_;
2073 my $result;
2075 # used for logging
2076 if (not defined $session_id) { $session_id = 0; }
2078 my $ldap_handle = &get_ldap_handle();
2079 if(defined($ldap_handle)) {
2080 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2081 my $mesg= $ldap_handle->search(
2082 base => $ldap_base,
2083 scope => 'sub',
2084 attrs => [],
2085 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2086 );
2087 if($mesg->{'resultCode'} == 0 &&
2088 $mesg->count != 0) {
2089 # Walk through all possible FAI container ou's
2090 my @sql_list;
2091 my $timestamp= &get_time();
2092 foreach my $ou (@{$mesg->{entries}}) {
2093 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2094 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2095 my @tmp_array=get_fai_release_entries($tmp_classes);
2096 if(@tmp_array) {
2097 foreach my $entry (@tmp_array) {
2098 if(defined($entry) && ref($entry) eq 'HASH') {
2099 my $sql=
2100 "INSERT INTO $table_name "
2101 ."(timestamp, release, class, type, state) VALUES ("
2102 .$timestamp.","
2103 ."'".$entry->{'release'}."',"
2104 ."'".$entry->{'class'}."',"
2105 ."'".$entry->{'type'}."',"
2106 ."'".$entry->{'state'}."')";
2107 push @sql_list, $sql;
2108 }
2109 }
2110 }
2111 }
2112 }
2114 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2115 if(@sql_list) {
2116 unshift @sql_list, "VACUUM";
2117 unshift @sql_list, "DELETE FROM $table_name";
2118 $fai_release_db->exec_statementlist(\@sql_list);
2119 }
2120 daemon_log("$session_id DEBUG: Done with inserting",7);
2121 }
2122 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2123 }
2124 $ldap_handle->disconnect;
2125 return $result;
2126 }
2128 sub get_fai_types {
2129 my $tmp_classes = shift || return undef;
2130 my @result;
2132 foreach my $type(keys %{$tmp_classes}) {
2133 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2134 my $entry = {
2135 type => $type,
2136 state => $tmp_classes->{$type}[0],
2137 };
2138 push @result, $entry;
2139 }
2140 }
2142 return @result;
2143 }
2145 sub get_fai_state {
2146 my $result = "";
2147 my $tmp_classes = shift || return $result;
2149 foreach my $type(keys %{$tmp_classes}) {
2150 if(defined($tmp_classes->{$type}[0])) {
2151 $result = $tmp_classes->{$type}[0];
2153 # State is equal for all types in class
2154 last;
2155 }
2156 }
2158 return $result;
2159 }
2161 sub resolve_fai_classes {
2162 my ($fai_base, $ldap_handle, $session_id) = @_;
2163 if (not defined $session_id) { $session_id = 0; }
2164 my $result;
2165 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2166 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2167 my $fai_classes;
2169 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2170 my $mesg= $ldap_handle->search(
2171 base => $fai_base,
2172 scope => 'sub',
2173 attrs => ['cn','objectClass','FAIstate'],
2174 filter => $fai_filter,
2175 );
2176 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2178 if($mesg->{'resultCode'} == 0 &&
2179 $mesg->count != 0) {
2180 foreach my $entry (@{$mesg->{entries}}) {
2181 if($entry->exists('cn')) {
2182 my $tmp_dn= $entry->dn();
2184 # Skip classname and ou dn parts for class
2185 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2187 # Skip classes without releases
2188 if((!defined($tmp_release)) || length($tmp_release)==0) {
2189 next;
2190 }
2192 my $tmp_cn= $entry->get_value('cn');
2193 my $tmp_state= $entry->get_value('FAIstate');
2195 my $tmp_type;
2196 # Get FAI type
2197 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2198 if(grep $_ eq $oclass, @possible_fai_classes) {
2199 $tmp_type= $oclass;
2200 last;
2201 }
2202 }
2204 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2205 # A Subrelease
2206 my @sub_releases = split(/,/, $tmp_release);
2208 # Walk through subreleases and build hash tree
2209 my $hash;
2210 while(my $tmp_sub_release = pop @sub_releases) {
2211 $hash .= "\{'$tmp_sub_release'\}->";
2212 }
2213 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2214 } else {
2215 # A branch, no subrelease
2216 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2217 }
2218 } elsif (!$entry->exists('cn')) {
2219 my $tmp_dn= $entry->dn();
2220 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2222 # Skip classes without releases
2223 if((!defined($tmp_release)) || length($tmp_release)==0) {
2224 next;
2225 }
2227 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2228 # A Subrelease
2229 my @sub_releases= split(/,/, $tmp_release);
2231 # Walk through subreleases and build hash tree
2232 my $hash;
2233 while(my $tmp_sub_release = pop @sub_releases) {
2234 $hash .= "\{'$tmp_sub_release'\}->";
2235 }
2236 # Remove the last two characters
2237 chop($hash);
2238 chop($hash);
2240 eval('$fai_classes->'.$hash.'= {}');
2241 } else {
2242 # A branch, no subrelease
2243 if(!exists($fai_classes->{$tmp_release})) {
2244 $fai_classes->{$tmp_release} = {};
2245 }
2246 }
2247 }
2248 }
2250 # The hash is complete, now we can honor the copy-on-write based missing entries
2251 foreach my $release (keys %$fai_classes) {
2252 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2253 }
2254 }
2255 return $result;
2256 }
2258 sub apply_fai_inheritance {
2259 my $fai_classes = shift || return {};
2260 my $tmp_classes;
2262 # Get the classes from the branch
2263 foreach my $class (keys %{$fai_classes}) {
2264 # Skip subreleases
2265 if($class =~ /^ou=.*$/) {
2266 next;
2267 } else {
2268 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2269 }
2270 }
2272 # Apply to each subrelease
2273 foreach my $subrelease (keys %{$fai_classes}) {
2274 if($subrelease =~ /ou=/) {
2275 foreach my $tmp_class (keys %{$tmp_classes}) {
2276 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2277 $fai_classes->{$subrelease}->{$tmp_class} =
2278 deep_copy($tmp_classes->{$tmp_class});
2279 } else {
2280 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2281 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2282 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2283 deep_copy($tmp_classes->{$tmp_class}->{$type});
2284 }
2285 }
2286 }
2287 }
2288 }
2289 }
2291 # Find subreleases in deeper levels
2292 foreach my $subrelease (keys %{$fai_classes}) {
2293 if($subrelease =~ /ou=/) {
2294 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2295 if($subsubrelease =~ /ou=/) {
2296 apply_fai_inheritance($fai_classes->{$subrelease});
2297 }
2298 }
2299 }
2300 }
2302 return $fai_classes;
2303 }
2305 sub get_fai_release_entries {
2306 my $tmp_classes = shift || return;
2307 my $parent = shift || "";
2308 my @result = shift || ();
2310 foreach my $entry (keys %{$tmp_classes}) {
2311 if(defined($entry)) {
2312 if($entry =~ /^ou=.*$/) {
2313 my $release_name = $entry;
2314 $release_name =~ s/ou=//g;
2315 if(length($parent)>0) {
2316 $release_name = $parent."/".$release_name;
2317 }
2318 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2319 foreach my $bufentry(@bufentries) {
2320 push @result, $bufentry;
2321 }
2322 } else {
2323 my @types = get_fai_types($tmp_classes->{$entry});
2324 foreach my $type (@types) {
2325 push @result,
2326 {
2327 'class' => $entry,
2328 'type' => $type->{'type'},
2329 'release' => $parent,
2330 'state' => $type->{'state'},
2331 };
2332 }
2333 }
2334 }
2335 }
2337 return @result;
2338 }
2340 sub deep_copy {
2341 my $this = shift;
2342 if (not ref $this) {
2343 $this;
2344 } elsif (ref $this eq "ARRAY") {
2345 [map deep_copy($_), @$this];
2346 } elsif (ref $this eq "HASH") {
2347 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2348 } else { die "what type is $_?" }
2349 }
2352 sub session_run_result {
2353 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2354 $kernel->sig(CHLD => "child_reap");
2355 }
2357 sub session_run_debug {
2358 my $result = $_[ARG0];
2359 print STDERR "$result\n";
2360 }
2362 sub session_run_done {
2363 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2364 delete $heap->{task}->{$task_id};
2365 }
2368 sub create_sources_list {
2369 my $session_id = shift;
2370 my $ldap_handle = &main::get_ldap_handle;
2371 my $result="/tmp/gosa_si_tmp_sources_list";
2373 # Remove old file
2374 if(stat($result)) {
2375 unlink($result);
2376 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2377 }
2379 my $fh;
2380 open($fh, ">$result");
2381 if (not defined $fh) {
2382 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2383 return undef;
2384 }
2385 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2386 my $mesg=$ldap_handle->search(
2387 base => $main::ldap_server_dn,
2388 scope => 'base',
2389 attrs => 'FAIrepository',
2390 filter => 'objectClass=FAIrepositoryServer'
2391 );
2392 if($mesg->count) {
2393 foreach my $entry(@{$mesg->{'entries'}}) {
2394 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2395 my ($server, $tag, $release, $sections)= split /\|/, $value;
2396 my $line = "deb $server $release";
2397 $sections =~ s/,/ /g;
2398 $line.= " $sections";
2399 print $fh $line."\n";
2400 }
2401 }
2402 }
2403 } else {
2404 if (defined $main::ldap_server_dn){
2405 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2406 } else {
2407 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2408 }
2409 }
2410 close($fh);
2412 return $result;
2413 }
2416 sub run_create_packages_list_db {
2417 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2418 my $session_id = $session->ID;
2420 my $task = POE::Wheel::Run->new(
2421 Priority => +20,
2422 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2423 StdoutEvent => "session_run_result",
2424 StderrEvent => "session_run_debug",
2425 CloseEvent => "session_run_done",
2426 );
2427 $heap->{task}->{ $task->ID } = $task;
2428 }
2431 sub create_packages_list_db {
2432 my ($ldap_handle, $sources_file, $session_id) = @_;
2434 # it should not be possible to trigger a recreation of packages_list_db
2435 # while packages_list_db is under construction, so set flag packages_list_under_construction
2436 # which is tested befor recreation can be started
2437 if (-r $packages_list_under_construction) {
2438 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2439 return;
2440 } else {
2441 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2442 # set packages_list_under_construction to true
2443 system("touch $packages_list_under_construction");
2444 @packages_list_statements=();
2445 }
2447 if (not defined $session_id) { $session_id = 0; }
2448 if (not defined $ldap_handle) {
2449 $ldap_handle= &get_ldap_handle();
2451 if (not defined $ldap_handle) {
2452 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2453 unlink($packages_list_under_construction);
2454 return;
2455 }
2456 }
2457 if (not defined $sources_file) {
2458 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2459 $sources_file = &create_sources_list($session_id);
2460 }
2462 if (not defined $sources_file) {
2463 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2464 unlink($packages_list_under_construction);
2465 return;
2466 }
2468 my $line;
2470 open(CONFIG, "<$sources_file") or do {
2471 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2472 unlink($packages_list_under_construction);
2473 return;
2474 };
2476 # Read lines
2477 while ($line = <CONFIG>){
2478 # Unify
2479 chop($line);
2480 $line =~ s/^\s+//;
2481 $line =~ s/^\s+/ /;
2483 # Strip comments
2484 $line =~ s/#.*$//g;
2486 # Skip empty lines
2487 if ($line =~ /^\s*$/){
2488 next;
2489 }
2491 # Interpret deb line
2492 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2493 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2494 my $section;
2495 foreach $section (split(' ', $sections)){
2496 &parse_package_info( $baseurl, $dist, $section, $session_id );
2497 }
2498 }
2499 }
2501 close (CONFIG);
2503 find(\&cleanup_and_extract, keys( %repo_dirs ));
2504 &main::strip_packages_list_statements();
2505 unshift @packages_list_statements, "VACUUM";
2506 $packages_list_db->exec_statementlist(\@packages_list_statements);
2507 unlink($packages_list_under_construction);
2508 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2509 return;
2510 }
2512 # This function should do some intensive task to minimize the db-traffic
2513 sub strip_packages_list_statements {
2514 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2515 my @new_statement_list=();
2516 my $hash;
2517 my $insert_hash;
2518 my $update_hash;
2519 my $delete_hash;
2520 my $local_timestamp=get_time();
2522 foreach my $existing_entry (@existing_entries) {
2523 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2524 }
2526 foreach my $statement (@packages_list_statements) {
2527 if($statement =~ /^INSERT/i) {
2528 # Assign the values from the insert statement
2529 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2530 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2531 if(exists($hash->{$distribution}->{$package}->{$version})) {
2532 # If section or description has changed, update the DB
2533 if(
2534 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2535 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2536 ) {
2537 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2538 }
2539 } else {
2540 # Insert a non-existing entry to db
2541 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2542 }
2543 } elsif ($statement =~ /^UPDATE/i) {
2544 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2545 /^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;
2546 foreach my $distribution (keys %{$hash}) {
2547 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2548 # update the insertion hash to execute only one query per package (insert instead insert+update)
2549 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2550 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2551 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2552 my $section;
2553 my $description;
2554 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2555 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2556 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2557 }
2558 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2559 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2560 }
2561 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2562 }
2563 }
2564 }
2565 }
2566 }
2568 # TODO: Check for orphaned entries
2570 # unroll the insert_hash
2571 foreach my $distribution (keys %{$insert_hash}) {
2572 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2573 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2574 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2575 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2576 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2577 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2578 ."'$local_timestamp')";
2579 }
2580 }
2581 }
2583 # unroll the update hash
2584 foreach my $distribution (keys %{$update_hash}) {
2585 foreach my $package (keys %{$update_hash->{$distribution}}) {
2586 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2587 my $set = "";
2588 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2589 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2590 }
2591 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2592 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2593 }
2594 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2595 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2596 }
2597 if(defined($set) and length($set) > 0) {
2598 $set .= "timestamp = '$local_timestamp'";
2599 } else {
2600 next;
2601 }
2602 push @new_statement_list,
2603 "UPDATE $main::packages_list_tn SET $set WHERE"
2604 ." distribution = '$distribution'"
2605 ." AND package = '$package'"
2606 ." AND version = '$version'";
2607 }
2608 }
2609 }
2611 @packages_list_statements = @new_statement_list;
2612 }
2615 sub parse_package_info {
2616 my ($baseurl, $dist, $section, $session_id)= @_;
2617 my ($package);
2618 if (not defined $session_id) { $session_id = 0; }
2619 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2620 $repo_dirs{ "${repo_path}/pool" } = 1;
2622 foreach $package ("Packages.gz"){
2623 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2624 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2625 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2626 }
2628 }
2631 sub get_package {
2632 my ($url, $dest, $session_id)= @_;
2633 if (not defined $session_id) { $session_id = 0; }
2635 my $tpath = dirname($dest);
2636 -d "$tpath" || mkpath "$tpath";
2638 # This is ugly, but I've no time to take a look at "how it works in perl"
2639 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2640 system("gunzip -cd '$dest' > '$dest.in'");
2641 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2642 unlink($dest);
2643 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2644 } else {
2645 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2646 }
2647 return 0;
2648 }
2651 sub parse_package {
2652 my ($path, $dist, $srv_path, $session_id)= @_;
2653 if (not defined $session_id) { $session_id = 0;}
2654 my ($package, $version, $section, $description);
2655 my $PACKAGES;
2656 my $timestamp = &get_time();
2658 if(not stat("$path.in")) {
2659 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2660 return;
2661 }
2663 open($PACKAGES, "<$path.in");
2664 if(not defined($PACKAGES)) {
2665 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2666 return;
2667 }
2669 # Read lines
2670 while (<$PACKAGES>){
2671 my $line = $_;
2672 # Unify
2673 chop($line);
2675 # Use empty lines as a trigger
2676 if ($line =~ /^\s*$/){
2677 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2678 push(@packages_list_statements, $sql);
2679 $package = "none";
2680 $version = "none";
2681 $section = "none";
2682 $description = "none";
2683 next;
2684 }
2686 # Trigger for package name
2687 if ($line =~ /^Package:\s/){
2688 ($package)= ($line =~ /^Package: (.*)$/);
2689 next;
2690 }
2692 # Trigger for version
2693 if ($line =~ /^Version:\s/){
2694 ($version)= ($line =~ /^Version: (.*)$/);
2695 next;
2696 }
2698 # Trigger for description
2699 if ($line =~ /^Description:\s/){
2700 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2701 next;
2702 }
2704 # Trigger for section
2705 if ($line =~ /^Section:\s/){
2706 ($section)= ($line =~ /^Section: (.*)$/);
2707 next;
2708 }
2710 # Trigger for filename
2711 if ($line =~ /^Filename:\s/){
2712 my ($filename) = ($line =~ /^Filename: (.*)$/);
2713 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2714 next;
2715 }
2716 }
2718 close( $PACKAGES );
2719 unlink( "$path.in" );
2720 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2721 }
2724 sub store_fileinfo {
2725 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2727 my %fileinfo = (
2728 'package' => $package,
2729 'dist' => $dist,
2730 'version' => $vers,
2731 );
2733 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2734 }
2737 sub cleanup_and_extract {
2738 my $fileinfo = $repo_files{ $File::Find::name };
2740 if( defined $fileinfo ) {
2742 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2743 my $sql;
2744 my $package = $fileinfo->{ 'package' };
2745 my $newver = $fileinfo->{ 'version' };
2747 mkpath($dir);
2748 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2750 if( -f "$dir/DEBIAN/templates" ) {
2752 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2754 my $tmpl= "";
2755 {
2756 local $/=undef;
2757 open FILE, "$dir/DEBIAN/templates";
2758 $tmpl = &encode_base64(<FILE>);
2759 close FILE;
2760 }
2761 rmtree("$dir/DEBIAN/templates");
2763 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2764 push @packages_list_statements, $sql;
2765 }
2766 }
2768 return;
2769 }
2772 sub register_at_foreign_servers {
2773 my ($kernel) = $_[KERNEL];
2775 # hole alle bekannten server aus known_server_db
2776 my $server_sql = "SELECT * FROM $known_server_tn";
2777 my $server_res = $known_server_db->exec_statement($server_sql);
2779 # no entries in known_server_db
2780 if (not ref(@$server_res[0]) eq "ARRAY") {
2781 # TODO
2782 }
2784 # detect already connected clients
2785 my $client_sql = "SELECT * FROM $known_clients_tn";
2786 my $client_res = $known_clients_db->exec_statement($client_sql);
2788 # send my server details to all other gosa-si-server within the network
2789 foreach my $hit (@$server_res) {
2790 my $hostname = @$hit[0];
2791 my $hostkey = &create_passwd;
2793 # add already connected clients to registration message
2794 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2795 &add_content2xml_hash($myhash, 'key', $hostkey);
2796 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2798 # build registration message and send it
2799 my $foreign_server_msg = &create_xml_string($myhash);
2800 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2801 }
2803 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2804 return;
2805 }
2808 #==== MAIN = main ==============================================================
2809 # parse commandline options
2810 Getopt::Long::Configure( "bundling" );
2811 GetOptions("h|help" => \&usage,
2812 "c|config=s" => \$cfg_file,
2813 "f|foreground" => \$foreground,
2814 "v|verbose+" => \$verbose,
2815 "no-bus+" => \$no_bus,
2816 "no-arp+" => \$no_arp,
2817 );
2819 # read and set config parameters
2820 &check_cmdline_param ;
2821 &read_configfile;
2822 &check_pid;
2824 $SIG{CHLD} = 'IGNORE';
2826 # forward error messages to logfile
2827 if( ! $foreground ) {
2828 open( STDIN, '+>/dev/null' );
2829 open( STDOUT, '+>&STDIN' );
2830 open( STDERR, '+>&STDIN' );
2831 }
2833 # Just fork, if we are not in foreground mode
2834 if( ! $foreground ) {
2835 chdir '/' or die "Can't chdir to /: $!";
2836 $pid = fork;
2837 setsid or die "Can't start a new session: $!";
2838 umask 0;
2839 } else {
2840 $pid = $$;
2841 }
2843 # Do something useful - put our PID into the pid_file
2844 if( 0 != $pid ) {
2845 open( LOCK_FILE, ">$pid_file" );
2846 print LOCK_FILE "$pid\n";
2847 close( LOCK_FILE );
2848 if( !$foreground ) {
2849 exit( 0 )
2850 };
2851 }
2853 # parse head url and revision from svn
2854 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2855 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2856 $server_headURL = defined $1 ? $1 : 'unknown' ;
2857 $server_revision = defined $2 ? $2 : 'unknown' ;
2858 if ($server_headURL =~ /\/tag\// ||
2859 $server_headURL =~ /\/branches\// ) {
2860 $server_status = "stable";
2861 } else {
2862 $server_status = "developmental" ;
2863 }
2866 daemon_log(" ", 1);
2867 daemon_log("$0 started!", 1);
2868 daemon_log("status: $server_status", 1);
2869 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2871 if ($no_bus > 0) {
2872 $bus_activ = "false"
2873 }
2875 # connect to incoming_db
2876 unlink($incoming_file_name);
2877 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2878 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2880 # connect to gosa-si job queue
2881 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2882 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2884 # connect to known_clients_db
2885 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2886 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2888 # connect to foreign_clients_db
2889 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2890 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2892 # connect to known_server_db
2893 unlink($known_server_file_name);
2894 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2895 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2897 # connect to login_usr_db
2898 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2899 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2901 # connect to fai_server_db and fai_release_db
2902 unlink($fai_server_file_name);
2903 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2904 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2906 unlink($fai_release_file_name);
2907 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2908 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2910 # connect to packages_list_db
2911 #unlink($packages_list_file_name);
2912 unlink($packages_list_under_construction);
2913 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2914 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2916 # connect to messaging_db
2917 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2918 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2921 # create xml object used for en/decrypting
2922 $xml = new XML::Simple();
2925 # foreign servers
2926 my @foreign_server_list;
2928 # add foreign server from cfg file
2929 if ($foreign_server_string ne "") {
2930 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2931 foreach my $foreign_server (@cfg_foreign_server_list) {
2932 push(@foreign_server_list, $foreign_server);
2933 }
2934 }
2936 # add foreign server from dns
2937 my @tmp_servers;
2938 if ( !$server_domain) {
2939 # Try our DNS Searchlist
2940 for my $domain(get_dns_domains()) {
2941 chomp($domain);
2942 my @tmp_domains= &get_server_addresses($domain);
2943 if(@tmp_domains) {
2944 for my $tmp_server(@tmp_domains) {
2945 push @tmp_servers, $tmp_server;
2946 }
2947 }
2948 }
2949 if(@tmp_servers && length(@tmp_servers)==0) {
2950 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2951 }
2952 } else {
2953 @tmp_servers = &get_server_addresses($server_domain);
2954 if( 0 == @tmp_servers ) {
2955 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2956 }
2957 }
2958 foreach my $server (@tmp_servers) {
2959 unshift(@foreign_server_list, $server);
2960 }
2961 # eliminate duplicate entries
2962 @foreign_server_list = &del_doubles(@foreign_server_list);
2963 my $all_foreign_server = join(", ", @foreign_server_list);
2964 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2966 # add all found foreign servers to known_server
2967 my $act_timestamp = &get_time();
2968 foreach my $foreign_server (@foreign_server_list) {
2969 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2970 primkey=>['hostname'],
2971 hostname=>$foreign_server,
2972 status=>'not_jet_registered',
2973 hostkey=>"none",
2974 timestamp=>$act_timestamp,
2975 } );
2976 }
2979 POE::Component::Server::TCP->new(
2980 Alias => "TCP_SERVER",
2981 Port => $server_port,
2982 ClientInput => sub {
2983 my ($kernel, $input) = @_[KERNEL, ARG0];
2984 push(@tasks, $input);
2985 push(@msgs_to_decrypt, $input);
2986 $kernel->yield("msg_to_decrypt");
2987 },
2988 InlineStates => {
2989 msg_to_decrypt => \&msg_to_decrypt,
2990 next_task => \&next_task,
2991 task_result => \&handle_task_result,
2992 task_done => \&handle_task_done,
2993 task_debug => \&handle_task_debug,
2994 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2995 }
2996 );
2998 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3000 # create session for repeatedly checking the job queue for jobs
3001 POE::Session->create(
3002 inline_states => {
3003 _start => \&session_start,
3004 register_at_foreign_servers => \®ister_at_foreign_servers,
3005 sig_handler => \&sig_handler,
3006 next_task => \&next_task,
3007 task_result => \&handle_task_result,
3008 task_done => \&handle_task_done,
3009 task_debug => \&handle_task_debug,
3010 watch_for_next_tasks => \&watch_for_next_tasks,
3011 watch_for_new_messages => \&watch_for_new_messages,
3012 watch_for_delivery_messages => \&watch_for_delivery_messages,
3013 watch_for_done_messages => \&watch_for_done_messages,
3014 watch_for_new_jobs => \&watch_for_new_jobs,
3015 watch_for_done_jobs => \&watch_for_done_jobs,
3016 watch_for_old_known_clients => \&watch_for_old_known_clients,
3017 create_packages_list_db => \&run_create_packages_list_db,
3018 create_fai_server_db => \&run_create_fai_server_db,
3019 create_fai_release_db => \&run_create_fai_release_db,
3020 session_run_result => \&session_run_result,
3021 session_run_debug => \&session_run_debug,
3022 session_run_done => \&session_run_done,
3023 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3024 }
3025 );
3028 # import all modules
3029 &import_modules;
3031 # TODO
3032 # check wether all modules are gosa-si valid passwd check
3036 POE::Kernel->run();
3037 exit;