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 my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1074 # target and source is equal to GOSA -> process here
1075 if (not $done) {
1076 if ($target eq "GOSA" && $source eq "GOSA") {
1077 $done = 1;
1078 }
1079 }
1081 # target is own address without forward_to_gosa-tag -> process here
1082 if (not $done) {
1083 if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1084 $done = 1;
1085 if ($source eq "GOSA") {
1086 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1087 }
1088 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1089 }
1090 }
1092 # target is a client address in known_clients -> process here
1093 if (not $done) {
1094 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1095 $res = $known_clients_db->select_dbentry($sql);
1096 if (keys(%$res) > 0) {
1097 $done = 1;
1098 my $hostname = $res->{1}->{'hostname'};
1099 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1100 print STDERR "target is a client address in known_clients -> process here\n";
1101 }
1102 }
1104 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1105 if (not $done) {
1106 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1107 my $gosa_at;
1108 my $gosa_session_id;
1109 if (($target eq $server_address) && (defined $forward_to_gosa)){
1110 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1111 if ($gosa_at ne $server_address) {
1112 $done = 1;
1113 print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1114 }
1115 }
1116 }
1118 # if message should be processed here -> add message to incoming_db
1119 if ($done) {
1121 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1122 # so gosa-si-server knows how to process this kind of messages
1123 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1124 $module = "GosaPackages";
1125 }
1127 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1128 primkey=>[],
1129 headertag=>$header,
1130 targettag=>$target,
1131 xmlmessage=>$msg,
1132 timestamp=>&get_time,
1133 module=>$module,
1134 sessionid=>$session_id,
1135 } );
1137 }
1139 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1140 if (not $done) {
1141 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1142 my $gosa_at;
1143 my $gosa_session_id;
1144 if (($target eq $server_address) && (defined $forward_to_gosa)){
1145 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1146 if ($gosa_at eq $server_address) {
1147 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1148 if( defined $session_reference ) {
1149 $heap = $session_reference->get_heap();
1150 }
1151 if(exists $heap->{'client'}) {
1152 $msg = &encrypt_msg($msg, $GosaPackages_key);
1153 $heap->{'client'}->put($msg);
1154 }
1155 $done = 1;
1156 print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1157 }
1158 }
1160 }
1162 # target is a client address in foreign_clients -> forward to registration server
1163 if (not $done) {
1164 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1165 $res = $foreign_clients_db->select_dbentry($sql);
1166 if (keys(%$res) > 0) {
1167 my $hostname = $res->{1}->{'hostname'};
1168 my $regserver = $res->{1}->{'regserver'};
1169 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1170 my $res = $known_server_db->select_dbentry($sql);
1171 if (keys(%$res) > 0) {
1172 my $regserver_key = $res->{1}->{'hostkey'};
1173 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1174 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1175 if ($source eq "GOSA") {
1176 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1177 }
1178 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1179 }
1180 $done = 1;
1181 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1182 }
1183 }
1185 # target is a server address -> forward to server
1186 if (not $done) {
1187 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1188 $res = $known_server_db->select_dbentry($sql);
1189 if (keys(%$res) > 0) {
1190 my $hostkey = $res->{1}->{'hostkey'};
1192 if ($source eq "GOSA") {
1193 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1194 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1196 }
1198 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1199 $done = 1;
1200 print STDERR "target is a server address -> forward to server\n";
1201 }
1204 }
1206 if (not $done) {
1207 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1208 }
1209 }
1211 return;
1212 }
1215 sub next_task {
1216 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1217 my $running_task = POE::Wheel::Run->new(
1218 Program => sub { process_task($session, $heap, $task) },
1219 StdioFilter => POE::Filter::Reference->new(),
1220 StdoutEvent => "task_result",
1221 StderrEvent => "task_debug",
1222 CloseEvent => "task_done",
1223 );
1224 $heap->{task}->{ $running_task->ID } = $running_task;
1225 }
1227 sub handle_task_result {
1228 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1229 my $client_answer = $result->{'answer'};
1230 if( $client_answer =~ s/session_id=(\d+)$// ) {
1231 my $session_id = $1;
1232 if( defined $session_id ) {
1233 my $session_reference = $kernel->ID_id_to_session($session_id);
1234 if( defined $session_reference ) {
1235 $heap = $session_reference->get_heap();
1236 }
1237 }
1239 if(exists $heap->{'client'}) {
1240 $heap->{'client'}->put($client_answer);
1241 }
1242 }
1243 $kernel->sig(CHLD => "child_reap");
1244 }
1246 sub handle_task_debug {
1247 my $result = $_[ARG0];
1248 print STDERR "$result\n";
1249 }
1251 sub handle_task_done {
1252 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1253 delete $heap->{task}->{$task_id};
1254 }
1256 sub process_task {
1257 no strict "refs";
1258 my ($session, $heap, $task) = @_;
1259 my $error = 0;
1260 my $answer_l;
1261 my ($answer_header, @answer_target_l, $answer_source);
1262 my $client_answer = "";
1264 # prepare all variables needed to process message
1265 my $msg = $task->{'xmlmessage'};
1266 my $incoming_id = $task->{'id'};
1267 my $module = $task->{'module'};
1268 my $header = $task->{'headertag'};
1269 my $session_id = $task->{'sessionid'};
1270 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1271 my $source = @{$msg_hash->{'source'}}[0];
1273 # set timestamp of incoming client uptodate, so client will not
1274 # be deleted from known_clients because of expiration
1275 my $act_time = &get_time();
1276 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1277 my $res = $known_clients_db->exec_statement($sql);
1279 ######################
1280 # process incoming msg
1281 if( $error == 0) {
1282 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1283 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1284 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1286 if ( 0 < @{$answer_l} ) {
1287 my $answer_str = join("\n", @{$answer_l});
1288 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1289 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1290 }
1291 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1292 } else {
1293 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1294 }
1296 }
1297 if( !$answer_l ) { $error++ };
1299 ########
1300 # answer
1301 if( $error == 0 ) {
1303 foreach my $answer ( @{$answer_l} ) {
1304 # check outgoing msg to xml validity
1305 my $answer_hash = &check_outgoing_xml_validity($answer);
1306 if( not defined $answer_hash ) { next; }
1308 $answer_header = @{$answer_hash->{'header'}}[0];
1309 @answer_target_l = @{$answer_hash->{'target'}};
1310 $answer_source = @{$answer_hash->{'source'}}[0];
1312 # deliver msg to all targets
1313 foreach my $answer_target ( @answer_target_l ) {
1315 # targets of msg are all gosa-si-clients in known_clients_db
1316 if( $answer_target eq "*" ) {
1317 # answer is for all clients
1318 my $sql_statement= "SELECT * FROM known_clients";
1319 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1320 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1321 my $host_name = $hit->{hostname};
1322 my $host_key = $hit->{hostkey};
1323 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1324 &update_jobdb_status_for_send_msgs($answer, $error);
1325 }
1326 }
1328 # targets of msg are all gosa-si-server in known_server_db
1329 elsif( $answer_target eq "KNOWN_SERVER" ) {
1330 # answer is for all server in known_server
1331 my $sql_statement= "SELECT * FROM $known_server_tn";
1332 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1333 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1334 my $host_name = $hit->{hostname};
1335 my $host_key = $hit->{hostkey};
1336 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1337 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1338 &update_jobdb_status_for_send_msgs($answer, $error);
1339 }
1340 }
1342 # target of msg is GOsa
1343 elsif( $answer_target eq "GOSA" ) {
1344 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1345 my $add_on = "";
1346 if( defined $session_id ) {
1347 $add_on = ".session_id=$session_id";
1348 }
1349 # answer is for GOSA and has to returned to connected client
1350 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1351 $client_answer = $gosa_answer.$add_on;
1352 }
1354 # target of msg is job queue at this host
1355 elsif( $answer_target eq "JOBDB") {
1356 $answer =~ /<header>(\S+)<\/header>/;
1357 my $header;
1358 if( defined $1 ) { $header = $1; }
1359 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1360 &update_jobdb_status_for_send_msgs($answer, $error);
1361 }
1363 # target of msg is a mac address
1364 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 ) {
1365 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1366 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1367 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1368 my $found_ip_flag = 0;
1369 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1370 my $host_name = $hit->{hostname};
1371 my $host_key = $hit->{hostkey};
1372 $answer =~ s/$answer_target/$host_name/g;
1373 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1374 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1375 &update_jobdb_status_for_send_msgs($answer, $error);
1376 $found_ip_flag++ ;
1377 }
1378 if( $found_ip_flag == 0) {
1379 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1380 if( $bus_activ eq "true" ) {
1381 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1382 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1383 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1384 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1385 my $bus_address = $hit->{hostname};
1386 my $bus_key = $hit->{hostkey};
1387 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1388 &update_jobdb_status_for_send_msgs($answer, $error);
1389 last;
1390 }
1391 }
1393 }
1395 # answer is for one specific host
1396 } else {
1397 # get encrypt_key
1398 my $encrypt_key = &get_encrypt_key($answer_target);
1399 if( not defined $encrypt_key ) {
1400 # unknown target, forward msg to bus
1401 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1402 if( $bus_activ eq "true" ) {
1403 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1404 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1405 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1406 my $res_length = keys( %{$query_res} );
1407 if( $res_length == 0 ){
1408 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1409 "no bus found in known_server", 3);
1410 }
1411 else {
1412 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1413 my $bus_key = $hit->{hostkey};
1414 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1415 &update_jobdb_status_for_send_msgs($answer, $error);
1416 }
1417 }
1418 }
1419 next;
1420 }
1421 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1422 &update_jobdb_status_for_send_msgs($answer, $error);
1423 }
1424 }
1425 }
1426 }
1428 my $filter = POE::Filter::Reference->new();
1429 my %result = (
1430 status => "seems ok to me",
1431 answer => $client_answer,
1432 );
1434 my $output = $filter->put( [ \%result ] );
1435 print @$output;
1438 }
1440 sub session_start {
1441 my ($kernel) = $_[KERNEL];
1442 &trigger_db_loop($kernel);
1443 $global_kernel = $kernel;
1444 $kernel->yield('register_at_foreign_servers');
1445 $kernel->yield('create_fai_server_db', $fai_server_tn );
1446 $kernel->yield('create_fai_release_db', $fai_release_tn );
1447 $kernel->yield('watch_for_next_tasks');
1448 $kernel->sig(USR1 => "sig_handler");
1449 $kernel->sig(USR2 => "create_packages_list_db");
1450 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1451 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1452 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1453 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1454 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1455 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1457 }
1459 sub trigger_db_loop {
1460 my ($kernel) = @_ ;
1461 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1462 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1463 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1464 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1465 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1466 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1467 }
1470 sub watch_for_done_jobs {
1471 my ($kernel,$heap) = @_[KERNEL, HEAP];
1473 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1474 my $res = $job_db->select_dbentry( $sql_statement );
1476 while( my ($id, $hit) = each %{$res} ) {
1477 my $jobdb_id = $hit->{id};
1478 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1479 my $res = $job_db->del_dbentry($sql_statement);
1480 }
1482 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1483 }
1486 sub watch_for_new_jobs {
1487 if($watch_for_new_jobs_in_progress == 0) {
1488 $watch_for_new_jobs_in_progress = 1;
1489 my ($kernel,$heap) = @_[KERNEL, HEAP];
1491 # check gosa job queue for jobs with executable timestamp
1492 my $timestamp = &get_time();
1493 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1494 my $res = $job_db->exec_statement( $sql_statement );
1496 # Merge all new jobs that would do the same actions
1497 my @drops;
1498 my $hits;
1499 foreach my $hit (reverse @{$res} ) {
1500 my $macaddress= lc @{$hit}[8];
1501 my $headertag= @{$hit}[5];
1502 if(
1503 defined($hits->{$macaddress}) &&
1504 defined($hits->{$macaddress}->{$headertag}) &&
1505 defined($hits->{$macaddress}->{$headertag}[0])
1506 ) {
1507 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1508 }
1509 $hits->{$macaddress}->{$headertag}= $hit;
1510 }
1512 # Delete new jobs with a matching job in state 'processing'
1513 foreach my $macaddress (keys %{$hits}) {
1514 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1515 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1516 if(defined($jobdb_id)) {
1517 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1518 my $res = $job_db->exec_statement( $sql_statement );
1519 foreach my $hit (@{$res}) {
1520 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1521 }
1522 } else {
1523 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1524 }
1525 }
1526 }
1528 # Commit deletion
1529 $job_db->exec_statementlist(\@drops);
1531 # Look for new jobs that could be executed
1532 foreach my $macaddress (keys %{$hits}) {
1534 # Look if there is an executing job
1535 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1536 my $res = $job_db->exec_statement( $sql_statement );
1538 # Skip new jobs for host if there is a processing job
1539 if(defined($res) and defined @{$res}[0]) {
1540 next;
1541 }
1543 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1544 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1545 if(defined($jobdb_id)) {
1546 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1548 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1549 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1550 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1552 # expect macaddress is unique!!!!!!
1553 my $target = $res_hash->{1}->{hostname};
1555 # change header
1556 $job_msg =~ s/<header>job_/<header>gosa_/;
1558 # add sqlite_id
1559 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1561 $job_msg =~ /<header>(\S+)<\/header>/;
1562 my $header = $1 ;
1563 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1565 # update status in job queue to 'processing'
1566 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1567 my $res = $job_db->update_dbentry($sql_statement);
1568 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1570 # We don't want parallel processing
1571 last;
1572 }
1573 }
1574 }
1576 $watch_for_new_jobs_in_progress = 0;
1577 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1578 }
1579 }
1582 sub watch_for_new_messages {
1583 my ($kernel,$heap) = @_[KERNEL, HEAP];
1584 my @coll_user_msg; # collection list of outgoing messages
1586 # check messaging_db for new incoming messages with executable timestamp
1587 my $timestamp = &get_time();
1588 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1589 my $res = $messaging_db->exec_statement( $sql_statement );
1590 foreach my $hit (@{$res}) {
1592 # create outgoing messages
1593 my $message_to = @{$hit}[3];
1594 # translate message_to to plain login name
1595 my @message_to_l = split(/,/, $message_to);
1596 my %receiver_h;
1597 foreach my $receiver (@message_to_l) {
1598 if ($receiver =~ /^u_([\s\S]*)$/) {
1599 $receiver_h{$1} = 0;
1600 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1601 my $group_name = $1;
1602 # fetch all group members from ldap and add them to receiver hash
1603 my $ldap_handle = &get_ldap_handle();
1604 if (defined $ldap_handle) {
1605 my $mesg = $ldap_handle->search(
1606 base => $ldap_base,
1607 scope => 'sub',
1608 attrs => ['memberUid'],
1609 filter => "cn=$group_name",
1610 );
1611 if ($mesg->count) {
1612 my @entries = $mesg->entries;
1613 foreach my $entry (@entries) {
1614 my @receivers= $entry->get_value("memberUid");
1615 foreach my $receiver (@receivers) {
1616 $receiver_h{$1} = 0;
1617 }
1618 }
1619 }
1620 # translating errors ?
1621 if ($mesg->code) {
1622 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1623 }
1624 # ldap handle error ?
1625 } else {
1626 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1627 }
1628 } else {
1629 my $sbjct = &encode_base64(@{$hit}[1]);
1630 my $msg = &encode_base64(@{$hit}[7]);
1631 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1632 }
1633 }
1634 my @receiver_l = keys(%receiver_h);
1636 my $message_id = @{$hit}[0];
1638 #add each outgoing msg to messaging_db
1639 my $receiver;
1640 foreach $receiver (@receiver_l) {
1641 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1642 "VALUES ('".
1643 $message_id."', '". # id
1644 @{$hit}[1]."', '". # subject
1645 @{$hit}[2]."', '". # message_from
1646 $receiver."', '". # message_to
1647 "none"."', '". # flag
1648 "out"."', '". # direction
1649 @{$hit}[6]."', '". # delivery_time
1650 @{$hit}[7]."', '". # message
1651 $timestamp."'". # timestamp
1652 ")";
1653 &daemon_log("M DEBUG: $sql_statement", 1);
1654 my $res = $messaging_db->exec_statement($sql_statement);
1655 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1656 }
1658 # set incoming message to flag d=deliverd
1659 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1660 &daemon_log("M DEBUG: $sql_statement", 7);
1661 $res = $messaging_db->update_dbentry($sql_statement);
1662 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1663 }
1665 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1666 return;
1667 }
1669 sub watch_for_delivery_messages {
1670 my ($kernel, $heap) = @_[KERNEL, HEAP];
1672 # select outgoing messages
1673 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1674 #&daemon_log("0 DEBUG: $sql", 7);
1675 my $res = $messaging_db->exec_statement( $sql_statement );
1677 # build out msg for each usr
1678 foreach my $hit (@{$res}) {
1679 my $receiver = @{$hit}[3];
1680 my $msg_id = @{$hit}[0];
1681 my $subject = @{$hit}[1];
1682 my $message = @{$hit}[7];
1684 # resolve usr -> host where usr is logged in
1685 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1686 #&daemon_log("0 DEBUG: $sql", 7);
1687 my $res = $login_users_db->exec_statement($sql);
1689 # reciver is logged in nowhere
1690 if (not ref(@$res[0]) eq "ARRAY") { next; }
1692 my $send_succeed = 0;
1693 foreach my $hit (@$res) {
1694 my $receiver_host = @$hit[0];
1695 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1697 # fetch key to encrypt msg propperly for usr/host
1698 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1699 &daemon_log("0 DEBUG: $sql", 7);
1700 my $res = $known_clients_db->select_dbentry($sql);
1702 # host is already down
1703 if (not ref(@$res[0]) eq "ARRAY") { next; }
1705 # host is on
1706 my $receiver_key = @{@{$res}[0]}[2];
1707 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1708 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1709 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1710 if ($error == 0 ) {
1711 $send_succeed++ ;
1712 }
1713 }
1715 if ($send_succeed) {
1716 # set outgoing msg at db to deliverd
1717 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1718 &daemon_log("0 DEBUG: $sql", 7);
1719 my $res = $messaging_db->exec_statement($sql);
1720 }
1721 }
1723 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1724 return;
1725 }
1728 sub watch_for_done_messages {
1729 my ($kernel,$heap) = @_[KERNEL, HEAP];
1731 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1732 #&daemon_log("0 DEBUG: $sql", 7);
1733 my $res = $messaging_db->exec_statement($sql);
1735 foreach my $hit (@{$res}) {
1736 my $msg_id = @{$hit}[0];
1738 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1739 #&daemon_log("0 DEBUG: $sql", 7);
1740 my $res = $messaging_db->exec_statement($sql);
1742 # not all usr msgs have been seen till now
1743 if ( ref(@$res[0]) eq "ARRAY") { next; }
1745 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1746 #&daemon_log("0 DEBUG: $sql", 7);
1747 $res = $messaging_db->exec_statement($sql);
1749 }
1751 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1752 return;
1753 }
1756 sub watch_for_old_known_clients {
1757 my ($kernel,$heap) = @_[KERNEL, HEAP];
1759 my $sql_statement = "SELECT * FROM $known_clients_tn";
1760 my $res = $known_clients_db->select_dbentry( $sql_statement );
1762 my $act_time = int(&get_time());
1764 while ( my ($hit_num, $hit) = each %$res) {
1765 my $expired_timestamp = int($hit->{'timestamp'});
1766 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1767 my $dt = DateTime->new( year => $1,
1768 month => $2,
1769 day => $3,
1770 hour => $4,
1771 minute => $5,
1772 second => $6,
1773 );
1775 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1776 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1777 if ($act_time > $expired_timestamp) {
1778 my $hostname = $hit->{'hostname'};
1779 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1780 my $del_res = $known_clients_db->exec_statement($del_sql);
1782 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1783 }
1785 }
1787 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1788 }
1791 sub watch_for_next_tasks {
1792 my ($kernel,$heap) = @_[KERNEL, HEAP];
1794 my $sql = "SELECT * FROM $incoming_tn";
1795 my $res = $incoming_db->select_dbentry($sql);
1797 while ( my ($hit_num, $hit) = each %$res) {
1798 my $headertag = $hit->{'headertag'};
1799 if ($headertag =~ /^answer_(\d+)/) {
1800 # do not start processing, this message is for a still running POE::Wheel
1801 next;
1802 }
1803 my $message_id = $hit->{'id'};
1804 $kernel->yield('next_task', $hit);
1806 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1807 my $res = $incoming_db->exec_statement($sql);
1808 }
1810 $kernel->delay_set('watch_for_next_tasks', 1);
1811 }
1814 sub get_ldap_handle {
1815 my ($session_id) = @_;
1816 my $heap;
1817 my $ldap_handle;
1819 if (not defined $session_id ) { $session_id = 0 };
1820 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1822 if ($session_id == 0) {
1823 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1824 $ldap_handle = Net::LDAP->new( $ldap_uri );
1825 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password);
1827 } else {
1828 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1829 if( defined $session_reference ) {
1830 $heap = $session_reference->get_heap();
1831 }
1833 if (not defined $heap) {
1834 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1835 return;
1836 }
1838 # TODO: This "if" is nonsense, because it doesn't prove that the
1839 # used handle is still valid - or if we've to reconnect...
1840 #if (not exists $heap->{ldap_handle}) {
1841 $ldap_handle = Net::LDAP->new( $ldap_uri );
1842 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1843 $heap->{ldap_handle} = $ldap_handle;
1844 #}
1845 }
1846 return $ldap_handle;
1847 }
1850 sub change_fai_state {
1851 my ($st, $targets, $session_id) = @_;
1852 $session_id = 0 if not defined $session_id;
1853 # Set FAI state to localboot
1854 my %mapActions= (
1855 reboot => '',
1856 update => 'softupdate',
1857 localboot => 'localboot',
1858 reinstall => 'install',
1859 rescan => '',
1860 wake => '',
1861 memcheck => 'memcheck',
1862 sysinfo => 'sysinfo',
1863 install => 'install',
1864 );
1866 # Return if this is unknown
1867 if (!exists $mapActions{ $st }){
1868 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1869 return;
1870 }
1872 my $state= $mapActions{ $st };
1874 my $ldap_handle = &get_ldap_handle($session_id);
1875 if( defined($ldap_handle) ) {
1877 # Build search filter for hosts
1878 my $search= "(&(objectClass=GOhard)";
1879 foreach (@{$targets}){
1880 $search.= "(macAddress=$_)";
1881 }
1882 $search.= ")";
1884 # If there's any host inside of the search string, procress them
1885 if (!($search =~ /macAddress/)){
1886 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1887 return;
1888 }
1890 # Perform search for Unit Tag
1891 my $mesg = $ldap_handle->search(
1892 base => $ldap_base,
1893 scope => 'sub',
1894 attrs => ['dn', 'FAIstate', 'objectClass'],
1895 filter => "$search"
1896 );
1898 if ($mesg->count) {
1899 my @entries = $mesg->entries;
1900 foreach my $entry (@entries) {
1901 # Only modify entry if it is not set to '$state'
1902 if ($entry->get_value("FAIstate") ne "$state"){
1903 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1904 my $result;
1905 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1906 if (exists $tmp{'FAIobject'}){
1907 if ($state eq ''){
1908 $result= $ldap_handle->modify($entry->dn, changes => [
1909 delete => [ FAIstate => [] ] ]);
1910 } else {
1911 $result= $ldap_handle->modify($entry->dn, changes => [
1912 replace => [ FAIstate => $state ] ]);
1913 }
1914 } elsif ($state ne ''){
1915 $result= $ldap_handle->modify($entry->dn, changes => [
1916 add => [ objectClass => 'FAIobject' ],
1917 add => [ FAIstate => $state ] ]);
1918 }
1920 # Errors?
1921 if ($result->code){
1922 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1923 }
1924 } else {
1925 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1926 }
1927 }
1928 }
1929 # if no ldap handle defined
1930 } else {
1931 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1932 }
1934 }
1937 sub change_goto_state {
1938 my ($st, $targets, $session_id) = @_;
1939 $session_id = 0 if not defined $session_id;
1941 # Switch on or off?
1942 my $state= $st eq 'active' ? 'active': 'locked';
1944 my $ldap_handle = &get_ldap_handle($session_id);
1945 if( defined($ldap_handle) ) {
1947 # Build search filter for hosts
1948 my $search= "(&(objectClass=GOhard)";
1949 foreach (@{$targets}){
1950 $search.= "(macAddress=$_)";
1951 }
1952 $search.= ")";
1954 # If there's any host inside of the search string, procress them
1955 if (!($search =~ /macAddress/)){
1956 return;
1957 }
1959 # Perform search for Unit Tag
1960 my $mesg = $ldap_handle->search(
1961 base => $ldap_base,
1962 scope => 'sub',
1963 attrs => ['dn', 'gotoMode'],
1964 filter => "$search"
1965 );
1967 if ($mesg->count) {
1968 my @entries = $mesg->entries;
1969 foreach my $entry (@entries) {
1971 # Only modify entry if it is not set to '$state'
1972 if ($entry->get_value("gotoMode") ne $state){
1974 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1975 my $result;
1976 $result= $ldap_handle->modify($entry->dn, changes => [
1977 replace => [ gotoMode => $state ] ]);
1979 # Errors?
1980 if ($result->code){
1981 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1982 }
1984 }
1985 }
1986 }
1988 }
1989 }
1992 sub run_create_fai_server_db {
1993 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1994 my $session_id = $session->ID;
1995 my $task = POE::Wheel::Run->new(
1996 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1997 StdoutEvent => "session_run_result",
1998 StderrEvent => "session_run_debug",
1999 CloseEvent => "session_run_done",
2000 );
2002 $heap->{task}->{ $task->ID } = $task;
2003 return;
2004 }
2007 sub create_fai_server_db {
2008 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2009 my $result;
2011 if (not defined $session_id) { $session_id = 0; }
2012 my $ldap_handle = &get_ldap_handle();
2013 if(defined($ldap_handle)) {
2014 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2015 my $mesg= $ldap_handle->search(
2016 base => $ldap_base,
2017 scope => 'sub',
2018 attrs => ['FAIrepository', 'gosaUnitTag'],
2019 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2020 );
2021 if($mesg->{'resultCode'} == 0 &&
2022 $mesg->count != 0) {
2023 foreach my $entry (@{$mesg->{entries}}) {
2024 if($entry->exists('FAIrepository')) {
2025 # Add an entry for each Repository configured for server
2026 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2027 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2028 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2029 $result= $fai_server_db->add_dbentry( {
2030 table => $table_name,
2031 primkey => ['server', 'release', 'tag'],
2032 server => $tmp_url,
2033 release => $tmp_release,
2034 sections => $tmp_sections,
2035 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2036 } );
2037 }
2038 }
2039 }
2040 }
2041 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2043 # TODO: Find a way to post the 'create_packages_list_db' event
2044 if(not defined($dont_create_packages_list)) {
2045 &create_packages_list_db(undef, undef, $session_id);
2046 }
2047 }
2049 $ldap_handle->disconnect;
2050 return $result;
2051 }
2054 sub run_create_fai_release_db {
2055 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2056 my $session_id = $session->ID;
2057 my $task = POE::Wheel::Run->new(
2058 Program => sub { &create_fai_release_db($table_name, $session_id) },
2059 StdoutEvent => "session_run_result",
2060 StderrEvent => "session_run_debug",
2061 CloseEvent => "session_run_done",
2062 );
2064 $heap->{task}->{ $task->ID } = $task;
2065 return;
2066 }
2069 sub create_fai_release_db {
2070 my ($table_name, $session_id) = @_;
2071 my $result;
2073 # used for logging
2074 if (not defined $session_id) { $session_id = 0; }
2076 my $ldap_handle = &get_ldap_handle();
2077 if(defined($ldap_handle)) {
2078 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2079 my $mesg= $ldap_handle->search(
2080 base => $ldap_base,
2081 scope => 'sub',
2082 attrs => [],
2083 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2084 );
2085 if($mesg->{'resultCode'} == 0 &&
2086 $mesg->count != 0) {
2087 # Walk through all possible FAI container ou's
2088 my @sql_list;
2089 my $timestamp= &get_time();
2090 foreach my $ou (@{$mesg->{entries}}) {
2091 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2092 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2093 my @tmp_array=get_fai_release_entries($tmp_classes);
2094 if(@tmp_array) {
2095 foreach my $entry (@tmp_array) {
2096 if(defined($entry) && ref($entry) eq 'HASH') {
2097 my $sql=
2098 "INSERT INTO $table_name "
2099 ."(timestamp, release, class, type, state) VALUES ("
2100 .$timestamp.","
2101 ."'".$entry->{'release'}."',"
2102 ."'".$entry->{'class'}."',"
2103 ."'".$entry->{'type'}."',"
2104 ."'".$entry->{'state'}."')";
2105 push @sql_list, $sql;
2106 }
2107 }
2108 }
2109 }
2110 }
2112 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2113 if(@sql_list) {
2114 unshift @sql_list, "VACUUM";
2115 unshift @sql_list, "DELETE FROM $table_name";
2116 $fai_release_db->exec_statementlist(\@sql_list);
2117 }
2118 daemon_log("$session_id DEBUG: Done with inserting",7);
2119 }
2120 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2121 }
2122 $ldap_handle->disconnect;
2123 return $result;
2124 }
2126 sub get_fai_types {
2127 my $tmp_classes = shift || return undef;
2128 my @result;
2130 foreach my $type(keys %{$tmp_classes}) {
2131 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2132 my $entry = {
2133 type => $type,
2134 state => $tmp_classes->{$type}[0],
2135 };
2136 push @result, $entry;
2137 }
2138 }
2140 return @result;
2141 }
2143 sub get_fai_state {
2144 my $result = "";
2145 my $tmp_classes = shift || return $result;
2147 foreach my $type(keys %{$tmp_classes}) {
2148 if(defined($tmp_classes->{$type}[0])) {
2149 $result = $tmp_classes->{$type}[0];
2151 # State is equal for all types in class
2152 last;
2153 }
2154 }
2156 return $result;
2157 }
2159 sub resolve_fai_classes {
2160 my ($fai_base, $ldap_handle, $session_id) = @_;
2161 if (not defined $session_id) { $session_id = 0; }
2162 my $result;
2163 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2164 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2165 my $fai_classes;
2167 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2168 my $mesg= $ldap_handle->search(
2169 base => $fai_base,
2170 scope => 'sub',
2171 attrs => ['cn','objectClass','FAIstate'],
2172 filter => $fai_filter,
2173 );
2174 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2176 if($mesg->{'resultCode'} == 0 &&
2177 $mesg->count != 0) {
2178 foreach my $entry (@{$mesg->{entries}}) {
2179 if($entry->exists('cn')) {
2180 my $tmp_dn= $entry->dn();
2182 # Skip classname and ou dn parts for class
2183 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2185 # Skip classes without releases
2186 if((!defined($tmp_release)) || length($tmp_release)==0) {
2187 next;
2188 }
2190 my $tmp_cn= $entry->get_value('cn');
2191 my $tmp_state= $entry->get_value('FAIstate');
2193 my $tmp_type;
2194 # Get FAI type
2195 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2196 if(grep $_ eq $oclass, @possible_fai_classes) {
2197 $tmp_type= $oclass;
2198 last;
2199 }
2200 }
2202 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2203 # A Subrelease
2204 my @sub_releases = split(/,/, $tmp_release);
2206 # Walk through subreleases and build hash tree
2207 my $hash;
2208 while(my $tmp_sub_release = pop @sub_releases) {
2209 $hash .= "\{'$tmp_sub_release'\}->";
2210 }
2211 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2212 } else {
2213 # A branch, no subrelease
2214 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2215 }
2216 } elsif (!$entry->exists('cn')) {
2217 my $tmp_dn= $entry->dn();
2218 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2220 # Skip classes without releases
2221 if((!defined($tmp_release)) || length($tmp_release)==0) {
2222 next;
2223 }
2225 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2226 # A Subrelease
2227 my @sub_releases= split(/,/, $tmp_release);
2229 # Walk through subreleases and build hash tree
2230 my $hash;
2231 while(my $tmp_sub_release = pop @sub_releases) {
2232 $hash .= "\{'$tmp_sub_release'\}->";
2233 }
2234 # Remove the last two characters
2235 chop($hash);
2236 chop($hash);
2238 eval('$fai_classes->'.$hash.'= {}');
2239 } else {
2240 # A branch, no subrelease
2241 if(!exists($fai_classes->{$tmp_release})) {
2242 $fai_classes->{$tmp_release} = {};
2243 }
2244 }
2245 }
2246 }
2248 # The hash is complete, now we can honor the copy-on-write based missing entries
2249 foreach my $release (keys %$fai_classes) {
2250 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2251 }
2252 }
2253 return $result;
2254 }
2256 sub apply_fai_inheritance {
2257 my $fai_classes = shift || return {};
2258 my $tmp_classes;
2260 # Get the classes from the branch
2261 foreach my $class (keys %{$fai_classes}) {
2262 # Skip subreleases
2263 if($class =~ /^ou=.*$/) {
2264 next;
2265 } else {
2266 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2267 }
2268 }
2270 # Apply to each subrelease
2271 foreach my $subrelease (keys %{$fai_classes}) {
2272 if($subrelease =~ /ou=/) {
2273 foreach my $tmp_class (keys %{$tmp_classes}) {
2274 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2275 $fai_classes->{$subrelease}->{$tmp_class} =
2276 deep_copy($tmp_classes->{$tmp_class});
2277 } else {
2278 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2279 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2280 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2281 deep_copy($tmp_classes->{$tmp_class}->{$type});
2282 }
2283 }
2284 }
2285 }
2286 }
2287 }
2289 # Find subreleases in deeper levels
2290 foreach my $subrelease (keys %{$fai_classes}) {
2291 if($subrelease =~ /ou=/) {
2292 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2293 if($subsubrelease =~ /ou=/) {
2294 apply_fai_inheritance($fai_classes->{$subrelease});
2295 }
2296 }
2297 }
2298 }
2300 return $fai_classes;
2301 }
2303 sub get_fai_release_entries {
2304 my $tmp_classes = shift || return;
2305 my $parent = shift || "";
2306 my @result = shift || ();
2308 foreach my $entry (keys %{$tmp_classes}) {
2309 if(defined($entry)) {
2310 if($entry =~ /^ou=.*$/) {
2311 my $release_name = $entry;
2312 $release_name =~ s/ou=//g;
2313 if(length($parent)>0) {
2314 $release_name = $parent."/".$release_name;
2315 }
2316 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2317 foreach my $bufentry(@bufentries) {
2318 push @result, $bufentry;
2319 }
2320 } else {
2321 my @types = get_fai_types($tmp_classes->{$entry});
2322 foreach my $type (@types) {
2323 push @result,
2324 {
2325 'class' => $entry,
2326 'type' => $type->{'type'},
2327 'release' => $parent,
2328 'state' => $type->{'state'},
2329 };
2330 }
2331 }
2332 }
2333 }
2335 return @result;
2336 }
2338 sub deep_copy {
2339 my $this = shift;
2340 if (not ref $this) {
2341 $this;
2342 } elsif (ref $this eq "ARRAY") {
2343 [map deep_copy($_), @$this];
2344 } elsif (ref $this eq "HASH") {
2345 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2346 } else { die "what type is $_?" }
2347 }
2350 sub session_run_result {
2351 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2352 $kernel->sig(CHLD => "child_reap");
2353 }
2355 sub session_run_debug {
2356 my $result = $_[ARG0];
2357 print STDERR "$result\n";
2358 }
2360 sub session_run_done {
2361 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2362 delete $heap->{task}->{$task_id};
2363 }
2366 sub create_sources_list {
2367 my $session_id = shift;
2368 my $ldap_handle = &main::get_ldap_handle;
2369 my $result="/tmp/gosa_si_tmp_sources_list";
2371 # Remove old file
2372 if(stat($result)) {
2373 unlink($result);
2374 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2375 }
2377 my $fh;
2378 open($fh, ">$result");
2379 if (not defined $fh) {
2380 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2381 return undef;
2382 }
2383 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2384 my $mesg=$ldap_handle->search(
2385 base => $main::ldap_server_dn,
2386 scope => 'base',
2387 attrs => 'FAIrepository',
2388 filter => 'objectClass=FAIrepositoryServer'
2389 );
2390 if($mesg->count) {
2391 foreach my $entry(@{$mesg->{'entries'}}) {
2392 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2393 my ($server, $tag, $release, $sections)= split /\|/, $value;
2394 my $line = "deb $server $release";
2395 $sections =~ s/,/ /g;
2396 $line.= " $sections";
2397 print $fh $line."\n";
2398 }
2399 }
2400 }
2401 } else {
2402 if (defined $main::ldap_server_dn){
2403 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2404 } else {
2405 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2406 }
2407 }
2408 close($fh);
2410 return $result;
2411 }
2414 sub run_create_packages_list_db {
2415 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2416 my $session_id = $session->ID;
2418 my $task = POE::Wheel::Run->new(
2419 Priority => +20,
2420 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2421 StdoutEvent => "session_run_result",
2422 StderrEvent => "session_run_debug",
2423 CloseEvent => "session_run_done",
2424 );
2425 $heap->{task}->{ $task->ID } = $task;
2426 }
2429 sub create_packages_list_db {
2430 my ($ldap_handle, $sources_file, $session_id) = @_;
2432 # it should not be possible to trigger a recreation of packages_list_db
2433 # while packages_list_db is under construction, so set flag packages_list_under_construction
2434 # which is tested befor recreation can be started
2435 if (-r $packages_list_under_construction) {
2436 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2437 return;
2438 } else {
2439 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2440 # set packages_list_under_construction to true
2441 system("touch $packages_list_under_construction");
2442 @packages_list_statements=();
2443 }
2445 if (not defined $session_id) { $session_id = 0; }
2446 if (not defined $ldap_handle) {
2447 $ldap_handle= &get_ldap_handle();
2449 if (not defined $ldap_handle) {
2450 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2451 unlink($packages_list_under_construction);
2452 return;
2453 }
2454 }
2455 if (not defined $sources_file) {
2456 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2457 $sources_file = &create_sources_list($session_id);
2458 }
2460 if (not defined $sources_file) {
2461 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2462 unlink($packages_list_under_construction);
2463 return;
2464 }
2466 my $line;
2468 open(CONFIG, "<$sources_file") or do {
2469 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2470 unlink($packages_list_under_construction);
2471 return;
2472 };
2474 # Read lines
2475 while ($line = <CONFIG>){
2476 # Unify
2477 chop($line);
2478 $line =~ s/^\s+//;
2479 $line =~ s/^\s+/ /;
2481 # Strip comments
2482 $line =~ s/#.*$//g;
2484 # Skip empty lines
2485 if ($line =~ /^\s*$/){
2486 next;
2487 }
2489 # Interpret deb line
2490 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2491 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2492 my $section;
2493 foreach $section (split(' ', $sections)){
2494 &parse_package_info( $baseurl, $dist, $section, $session_id );
2495 }
2496 }
2497 }
2499 close (CONFIG);
2501 find(\&cleanup_and_extract, keys( %repo_dirs ));
2502 &main::strip_packages_list_statements();
2503 unshift @packages_list_statements, "VACUUM";
2504 $packages_list_db->exec_statementlist(\@packages_list_statements);
2505 unlink($packages_list_under_construction);
2506 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2507 return;
2508 }
2510 # This function should do some intensive task to minimize the db-traffic
2511 sub strip_packages_list_statements {
2512 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2513 my @new_statement_list=();
2514 my $hash;
2515 my $insert_hash;
2516 my $update_hash;
2517 my $delete_hash;
2518 my $local_timestamp=get_time();
2520 foreach my $existing_entry (@existing_entries) {
2521 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2522 }
2524 foreach my $statement (@packages_list_statements) {
2525 if($statement =~ /^INSERT/i) {
2526 # Assign the values from the insert statement
2527 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2528 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2529 if(exists($hash->{$distribution}->{$package}->{$version})) {
2530 # If section or description has changed, update the DB
2531 if(
2532 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2533 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2534 ) {
2535 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2536 }
2537 } else {
2538 # Insert a non-existing entry to db
2539 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2540 }
2541 } elsif ($statement =~ /^UPDATE/i) {
2542 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2543 /^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;
2544 foreach my $distribution (keys %{$hash}) {
2545 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2546 # update the insertion hash to execute only one query per package (insert instead insert+update)
2547 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2548 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2549 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2550 my $section;
2551 my $description;
2552 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2553 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2554 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2555 }
2556 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2557 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2558 }
2559 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2560 }
2561 }
2562 }
2563 }
2564 }
2566 # TODO: Check for orphaned entries
2568 # unroll the insert_hash
2569 foreach my $distribution (keys %{$insert_hash}) {
2570 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2571 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2572 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2573 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2574 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2575 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2576 ."'$local_timestamp')";
2577 }
2578 }
2579 }
2581 # unroll the update hash
2582 foreach my $distribution (keys %{$update_hash}) {
2583 foreach my $package (keys %{$update_hash->{$distribution}}) {
2584 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2585 my $set = "";
2586 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2587 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2588 }
2589 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2590 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2591 }
2592 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2593 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2594 }
2595 if(defined($set) and length($set) > 0) {
2596 $set .= "timestamp = '$local_timestamp'";
2597 } else {
2598 next;
2599 }
2600 push @new_statement_list,
2601 "UPDATE $main::packages_list_tn SET $set WHERE"
2602 ." distribution = '$distribution'"
2603 ." AND package = '$package'"
2604 ." AND version = '$version'";
2605 }
2606 }
2607 }
2609 @packages_list_statements = @new_statement_list;
2610 }
2613 sub parse_package_info {
2614 my ($baseurl, $dist, $section, $session_id)= @_;
2615 my ($package);
2616 if (not defined $session_id) { $session_id = 0; }
2617 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2618 $repo_dirs{ "${repo_path}/pool" } = 1;
2620 foreach $package ("Packages.gz"){
2621 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2622 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2623 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2624 }
2626 }
2629 sub get_package {
2630 my ($url, $dest, $session_id)= @_;
2631 if (not defined $session_id) { $session_id = 0; }
2633 my $tpath = dirname($dest);
2634 -d "$tpath" || mkpath "$tpath";
2636 # This is ugly, but I've no time to take a look at "how it works in perl"
2637 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2638 system("gunzip -cd '$dest' > '$dest.in'");
2639 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2640 unlink($dest);
2641 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2642 } else {
2643 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2644 }
2645 return 0;
2646 }
2649 sub parse_package {
2650 my ($path, $dist, $srv_path, $session_id)= @_;
2651 if (not defined $session_id) { $session_id = 0;}
2652 my ($package, $version, $section, $description);
2653 my $PACKAGES;
2654 my $timestamp = &get_time();
2656 if(not stat("$path.in")) {
2657 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2658 return;
2659 }
2661 open($PACKAGES, "<$path.in");
2662 if(not defined($PACKAGES)) {
2663 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2664 return;
2665 }
2667 # Read lines
2668 while (<$PACKAGES>){
2669 my $line = $_;
2670 # Unify
2671 chop($line);
2673 # Use empty lines as a trigger
2674 if ($line =~ /^\s*$/){
2675 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2676 push(@packages_list_statements, $sql);
2677 $package = "none";
2678 $version = "none";
2679 $section = "none";
2680 $description = "none";
2681 next;
2682 }
2684 # Trigger for package name
2685 if ($line =~ /^Package:\s/){
2686 ($package)= ($line =~ /^Package: (.*)$/);
2687 next;
2688 }
2690 # Trigger for version
2691 if ($line =~ /^Version:\s/){
2692 ($version)= ($line =~ /^Version: (.*)$/);
2693 next;
2694 }
2696 # Trigger for description
2697 if ($line =~ /^Description:\s/){
2698 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2699 next;
2700 }
2702 # Trigger for section
2703 if ($line =~ /^Section:\s/){
2704 ($section)= ($line =~ /^Section: (.*)$/);
2705 next;
2706 }
2708 # Trigger for filename
2709 if ($line =~ /^Filename:\s/){
2710 my ($filename) = ($line =~ /^Filename: (.*)$/);
2711 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2712 next;
2713 }
2714 }
2716 close( $PACKAGES );
2717 unlink( "$path.in" );
2718 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2719 }
2722 sub store_fileinfo {
2723 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2725 my %fileinfo = (
2726 'package' => $package,
2727 'dist' => $dist,
2728 'version' => $vers,
2729 );
2731 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2732 }
2735 sub cleanup_and_extract {
2736 my $fileinfo = $repo_files{ $File::Find::name };
2738 if( defined $fileinfo ) {
2740 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2741 my $sql;
2742 my $package = $fileinfo->{ 'package' };
2743 my $newver = $fileinfo->{ 'version' };
2745 mkpath($dir);
2746 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2748 if( -f "$dir/DEBIAN/templates" ) {
2750 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2752 my $tmpl= "";
2753 {
2754 local $/=undef;
2755 open FILE, "$dir/DEBIAN/templates";
2756 $tmpl = &encode_base64(<FILE>);
2757 close FILE;
2758 }
2759 rmtree("$dir/DEBIAN/templates");
2761 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2762 push @packages_list_statements, $sql;
2763 }
2764 }
2766 return;
2767 }
2770 sub register_at_foreign_servers {
2771 my ($kernel) = $_[KERNEL];
2773 # hole alle bekannten server aus known_server_db
2774 my $server_sql = "SELECT * FROM $known_server_tn";
2775 my $server_res = $known_server_db->exec_statement($server_sql);
2777 # no entries in known_server_db
2778 if (not ref(@$server_res[0]) eq "ARRAY") {
2779 # TODO
2780 }
2782 # detect already connected clients
2783 my $client_sql = "SELECT * FROM $known_clients_tn";
2784 my $client_res = $known_clients_db->exec_statement($client_sql);
2786 # send my server details to all other gosa-si-server within the network
2787 foreach my $hit (@$server_res) {
2788 my $hostname = @$hit[0];
2789 my $hostkey = &create_passwd;
2791 # add already connected clients to registration message
2792 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2793 &add_content2xml_hash($myhash, 'key', $hostkey);
2794 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2796 # build registration message and send it
2797 my $foreign_server_msg = &create_xml_string($myhash);
2798 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2799 }
2801 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2802 return;
2803 }
2806 #==== MAIN = main ==============================================================
2807 # parse commandline options
2808 Getopt::Long::Configure( "bundling" );
2809 GetOptions("h|help" => \&usage,
2810 "c|config=s" => \$cfg_file,
2811 "f|foreground" => \$foreground,
2812 "v|verbose+" => \$verbose,
2813 "no-bus+" => \$no_bus,
2814 "no-arp+" => \$no_arp,
2815 );
2817 # read and set config parameters
2818 &check_cmdline_param ;
2819 &read_configfile;
2820 &check_pid;
2822 $SIG{CHLD} = 'IGNORE';
2824 # forward error messages to logfile
2825 if( ! $foreground ) {
2826 open( STDIN, '+>/dev/null' );
2827 open( STDOUT, '+>&STDIN' );
2828 open( STDERR, '+>&STDIN' );
2829 }
2831 # Just fork, if we are not in foreground mode
2832 if( ! $foreground ) {
2833 chdir '/' or die "Can't chdir to /: $!";
2834 $pid = fork;
2835 setsid or die "Can't start a new session: $!";
2836 umask 0;
2837 } else {
2838 $pid = $$;
2839 }
2841 # Do something useful - put our PID into the pid_file
2842 if( 0 != $pid ) {
2843 open( LOCK_FILE, ">$pid_file" );
2844 print LOCK_FILE "$pid\n";
2845 close( LOCK_FILE );
2846 if( !$foreground ) {
2847 exit( 0 )
2848 };
2849 }
2851 # parse head url and revision from svn
2852 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2853 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2854 $server_headURL = defined $1 ? $1 : 'unknown' ;
2855 $server_revision = defined $2 ? $2 : 'unknown' ;
2856 if ($server_headURL =~ /\/tag\// ||
2857 $server_headURL =~ /\/branches\// ) {
2858 $server_status = "stable";
2859 } else {
2860 $server_status = "developmental" ;
2861 }
2864 daemon_log(" ", 1);
2865 daemon_log("$0 started!", 1);
2866 daemon_log("status: $server_status", 1);
2867 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2869 if ($no_bus > 0) {
2870 $bus_activ = "false"
2871 }
2873 # connect to incoming_db
2874 unlink($incoming_file_name);
2875 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2876 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2878 # connect to gosa-si job queue
2879 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2880 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2882 # connect to known_clients_db
2883 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2884 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2886 # connect to foreign_clients_db
2887 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2888 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2890 # connect to known_server_db
2891 unlink($known_server_file_name);
2892 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2893 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2895 # connect to login_usr_db
2896 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2897 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2899 # connect to fai_server_db and fai_release_db
2900 unlink($fai_server_file_name);
2901 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2902 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2904 unlink($fai_release_file_name);
2905 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2906 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2908 # connect to packages_list_db
2909 #unlink($packages_list_file_name);
2910 unlink($packages_list_under_construction);
2911 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2912 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2914 # connect to messaging_db
2915 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2916 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2919 # create xml object used for en/decrypting
2920 $xml = new XML::Simple();
2923 # foreign servers
2924 my @foreign_server_list;
2926 # add foreign server from cfg file
2927 if ($foreign_server_string ne "") {
2928 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2929 foreach my $foreign_server (@cfg_foreign_server_list) {
2930 push(@foreign_server_list, $foreign_server);
2931 }
2932 }
2934 # add foreign server from dns
2935 my @tmp_servers;
2936 if ( !$server_domain) {
2937 # Try our DNS Searchlist
2938 for my $domain(get_dns_domains()) {
2939 chomp($domain);
2940 my @tmp_domains= &get_server_addresses($domain);
2941 if(@tmp_domains) {
2942 for my $tmp_server(@tmp_domains) {
2943 push @tmp_servers, $tmp_server;
2944 }
2945 }
2946 }
2947 if(@tmp_servers && length(@tmp_servers)==0) {
2948 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2949 }
2950 } else {
2951 @tmp_servers = &get_server_addresses($server_domain);
2952 if( 0 == @tmp_servers ) {
2953 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2954 }
2955 }
2956 foreach my $server (@tmp_servers) {
2957 unshift(@foreign_server_list, $server);
2958 }
2959 # eliminate duplicate entries
2960 @foreign_server_list = &del_doubles(@foreign_server_list);
2961 my $all_foreign_server = join(", ", @foreign_server_list);
2962 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2964 # add all found foreign servers to known_server
2965 my $act_timestamp = &get_time();
2966 foreach my $foreign_server (@foreign_server_list) {
2967 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2968 primkey=>['hostname'],
2969 hostname=>$foreign_server,
2970 status=>'not_jet_registered',
2971 hostkey=>"none",
2972 timestamp=>$act_timestamp,
2973 } );
2974 }
2977 POE::Component::Server::TCP->new(
2978 Alias => "TCP_SERVER",
2979 Port => $server_port,
2980 ClientInput => sub {
2981 my ($kernel, $input) = @_[KERNEL, ARG0];
2982 push(@tasks, $input);
2983 push(@msgs_to_decrypt, $input);
2984 $kernel->yield("msg_to_decrypt");
2985 },
2986 InlineStates => {
2987 msg_to_decrypt => \&msg_to_decrypt,
2988 next_task => \&next_task,
2989 task_result => \&handle_task_result,
2990 task_done => \&handle_task_done,
2991 task_debug => \&handle_task_debug,
2992 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2993 }
2994 );
2996 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2998 # create session for repeatedly checking the job queue for jobs
2999 POE::Session->create(
3000 inline_states => {
3001 _start => \&session_start,
3002 register_at_foreign_servers => \®ister_at_foreign_servers,
3003 sig_handler => \&sig_handler,
3004 next_task => \&next_task,
3005 task_result => \&handle_task_result,
3006 task_done => \&handle_task_done,
3007 task_debug => \&handle_task_debug,
3008 watch_for_next_tasks => \&watch_for_next_tasks,
3009 watch_for_new_messages => \&watch_for_new_messages,
3010 watch_for_delivery_messages => \&watch_for_delivery_messages,
3011 watch_for_done_messages => \&watch_for_done_messages,
3012 watch_for_new_jobs => \&watch_for_new_jobs,
3013 watch_for_done_jobs => \&watch_for_done_jobs,
3014 watch_for_old_known_clients => \&watch_for_old_known_clients,
3015 create_packages_list_db => \&run_create_packages_list_db,
3016 create_fai_server_db => \&run_create_fai_server_db,
3017 create_fai_release_db => \&run_create_fai_release_db,
3018 session_run_result => \&session_run_result,
3019 session_run_debug => \&session_run_debug,
3020 session_run_done => \&session_run_done,
3021 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3022 }
3023 );
3026 # import all modules
3027 &import_modules;
3029 # TODO
3030 # check wether all modules are gosa-si valid passwd check
3034 POE::Kernel->run();
3035 exit;