250e0080b376831bdaced8405381f5de7cfee7bb
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $gosa_address;
96 our $no_arp;
97 our $verbose;
98 our $forground;
99 our $cfg_file;
100 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # dak variables
103 our $dak_base_directory;
104 our $dak_signing_keys_directory;
105 our $dak_queue_directory;
106 our $dak_user;
108 # specifies the verbosity of the daemon_log
109 $verbose = 0 ;
111 # if foreground is not null, script will be not forked to background
112 $foreground = 0 ;
114 # specifies the timeout seconds while checking the online status of a registrating client
115 $ping_timeout = 5;
117 $no_arp = 0;
118 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
119 my @packages_list_statements;
120 my $watch_for_new_jobs_in_progress = 0;
122 # holds all incoming decrypted messages
123 our $incoming_db;
124 our $incoming_tn = 'incoming';
125 my $incoming_file_name;
126 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
127 "timestamp DEFAULT 'none'",
128 "headertag DEFAULT 'none'",
129 "targettag DEFAULT 'none'",
130 "xmlmessage DEFAULT 'none'",
131 "module DEFAULT 'none'",
132 "sessionid DEFAULT '0'",
133 );
135 # holds all gosa jobs
136 our $job_db;
137 our $job_queue_tn = 'jobs';
138 my $job_queue_file_name;
139 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
140 "timestamp DEFAULT 'none'",
141 "status DEFAULT 'none'",
142 "result DEFAULT 'none'",
143 "progress DEFAULT 'none'",
144 "headertag DEFAULT 'none'",
145 "targettag DEFAULT 'none'",
146 "xmlmessage DEFAULT 'none'",
147 "macaddress DEFAULT 'none'",
148 "plainname DEFAULT 'none'",
149 "siserver DEFAULT 'none'",
150 "modified DEFAULT '0'",
151 );
153 # holds all other gosa-si-server
154 our $known_server_db;
155 our $known_server_tn = "known_server";
156 my $known_server_file_name;
157 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
159 # holds all registrated clients
160 our $known_clients_db;
161 our $known_clients_tn = "known_clients";
162 my $known_clients_file_name;
163 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
165 # holds all registered clients at a foreign server
166 our $foreign_clients_db;
167 our $foreign_clients_tn = "foreign_clients";
168 my $foreign_clients_file_name;
169 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
171 # holds all logged in user at each client
172 our $login_users_db;
173 our $login_users_tn = "login_users";
174 my $login_users_file_name;
175 my @login_users_col_names = ("client", "user", "timestamp");
177 # holds all fai server, the debian release and tag
178 our $fai_server_db;
179 our $fai_server_tn = "fai_server";
180 my $fai_server_file_name;
181 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
183 our $fai_release_db;
184 our $fai_release_tn = "fai_release";
185 my $fai_release_file_name;
186 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
188 # holds all packages available from different repositories
189 our $packages_list_db;
190 our $packages_list_tn = "packages_list";
191 my $packages_list_file_name;
192 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
193 my $outdir = "/tmp/packages_list_db";
194 my $arch = "i386";
196 # holds all messages which should be delivered to a user
197 our $messaging_db;
198 our $messaging_tn = "messaging";
199 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
200 "flag", "direction", "delivery_time", "message", "timestamp" );
201 my $messaging_file_name;
203 # path to directory to store client install log files
204 our $client_fai_log_dir = "/var/log/fai";
206 # queue which stores taskes until one of the $max_children children are ready to process the task
207 my @tasks = qw();
208 my @msgs_to_decrypt = qw();
209 my $max_children = 2;
212 %cfg_defaults = (
213 "general" => {
214 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
215 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
216 },
217 "server" => {
218 "port" => [\$server_port, "20081"],
219 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
229 "repo-path" => [\$repo_path, '/srv/www/repository'],
230 "ldap-uri" => [\$ldap_uri, ""],
231 "ldap-base" => [\$ldap_base, ""],
232 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
233 "ldap-admin-password" => [\$ldap_admin_password, ""],
234 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
235 "max-clients" => [\$max_clients, 10],
236 "wol-password" => [\$wake_on_lan_passwd, ""],
237 },
238 "GOsaPackages" => {
239 "ip" => [\$gosa_ip, "0.0.0.0"],
240 "port" => [\$gosa_port, "20082"],
241 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
242 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
243 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
244 "key" => [\$GosaPackages_key, "none"],
245 "dak-base" => [\$dak_base_directory, "/srv/archive"],
246 "dak-keyring" => [\$dak_signing_keys_directory, "/srv/archive/keyrings"],
247 "dak-queue" => [\$dak_queue_directory, "/srv/archive/queue"],
248 "dak-user" => [\$dak_user, "deb-dak"],
249 },
250 "ClientPackages" => {
251 "key" => [\$ClientPackages_key, "none"],
252 },
253 "ServerPackages"=> {
254 "address" => [\$foreign_server_string, ""],
255 "domain" => [\$server_domain, ""],
256 "key" => [\$ServerPackages_key, "none"],
257 "key-lifetime" => [\$foreign_servers_register_delay, 120],
258 "job-synchronization-enabled" => [\$job_synchronization, "true"],
259 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
260 }
261 );
264 #=== FUNCTION ================================================================
265 # NAME: usage
266 # PARAMETERS: nothing
267 # RETURNS: nothing
268 # DESCRIPTION: print out usage text to STDERR
269 #===============================================================================
270 sub usage {
271 print STDERR << "EOF" ;
272 usage: $prg [-hvf] [-c config]
274 -h : this (help) message
275 -c <file> : config file
276 -f : foreground, process will not be forked to background
277 -v : be verbose (multiple to increase verbosity)
278 -no-arp : starts $prg without connection to arp module
280 EOF
281 print "\n" ;
282 }
285 #=== FUNCTION ================================================================
286 # NAME: read_configfile
287 # PARAMETERS: cfg_file - string -
288 # RETURNS: nothing
289 # DESCRIPTION: read cfg_file and set variables
290 #===============================================================================
291 sub read_configfile {
292 my $cfg;
293 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
294 if( -r $cfg_file ) {
295 $cfg = Config::IniFiles->new( -file => $cfg_file );
296 } else {
297 print STDERR "Couldn't read config file!\n";
298 }
299 } else {
300 $cfg = Config::IniFiles->new() ;
301 }
302 foreach my $section (keys %cfg_defaults) {
303 foreach my $param (keys %{$cfg_defaults{ $section }}) {
304 my $pinfo = $cfg_defaults{ $section }{ $param };
305 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
306 }
307 }
308 }
311 #=== FUNCTION ================================================================
312 # NAME: logging
313 # PARAMETERS: level - string - default 'info'
314 # msg - string -
315 # facility - string - default 'LOG_DAEMON'
316 # RETURNS: nothing
317 # DESCRIPTION: function for logging
318 #===============================================================================
319 sub daemon_log {
320 # log into log_file
321 my( $msg, $level ) = @_;
322 if(not defined $msg) { return }
323 if(not defined $level) { $level = 1 }
324 if(defined $log_file){
325 open(LOG_HANDLE, ">>$log_file");
326 chmod 0600, $log_file;
327 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
328 print STDERR "cannot open $log_file: $!";
329 return
330 }
331 chomp($msg);
332 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
333 if($level <= $verbose){
334 my ($seconds, $minutes, $hours, $monthday, $month,
335 $year, $weekday, $yearday, $sommertime) = localtime(time);
336 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
337 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
338 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
339 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
340 $month = $monthnames[$month];
341 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
342 $year+=1900;
343 my $name = $prg;
345 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
346 print LOG_HANDLE $log_msg;
347 if( $foreground ) {
348 print STDERR $log_msg;
349 }
350 }
351 close( LOG_HANDLE );
352 }
353 }
356 #=== FUNCTION ================================================================
357 # NAME: check_cmdline_param
358 # PARAMETERS: nothing
359 # RETURNS: nothing
360 # DESCRIPTION: validates commandline parameter
361 #===============================================================================
362 sub check_cmdline_param () {
363 my $err_config;
364 my $err_counter = 0;
365 if(not defined($cfg_file)) {
366 $cfg_file = "/etc/gosa-si/server.conf";
367 if(! -r $cfg_file) {
368 $err_config = "please specify a config file";
369 $err_counter += 1;
370 }
371 }
372 if( $err_counter > 0 ) {
373 &usage( "", 1 );
374 if( defined( $err_config)) { print STDERR "$err_config\n"}
375 print STDERR "\n";
376 exit( -1 );
377 }
378 }
381 #=== FUNCTION ================================================================
382 # NAME: check_pid
383 # PARAMETERS: nothing
384 # RETURNS: nothing
385 # DESCRIPTION: handels pid processing
386 #===============================================================================
387 sub check_pid {
388 $pid = -1;
389 # Check, if we are already running
390 if( open(LOCK_FILE, "<$pid_file") ) {
391 $pid = <LOCK_FILE>;
392 if( defined $pid ) {
393 chomp( $pid );
394 if( -f "/proc/$pid/stat" ) {
395 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
396 if( $stat ) {
397 daemon_log("ERROR: Already running",1);
398 close( LOCK_FILE );
399 exit -1;
400 }
401 }
402 }
403 close( LOCK_FILE );
404 unlink( $pid_file );
405 }
407 # create a syslog msg if it is not to possible to open PID file
408 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
409 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
410 if (open(LOCK_FILE, '<', $pid_file)
411 && ($pid = <LOCK_FILE>))
412 {
413 chomp($pid);
414 $msg .= "(PID $pid)\n";
415 } else {
416 $msg .= "(unable to read PID)\n";
417 }
418 if( ! ($foreground) ) {
419 openlog( $0, "cons,pid", "daemon" );
420 syslog( "warning", $msg );
421 closelog();
422 }
423 else {
424 print( STDERR " $msg " );
425 }
426 exit( -1 );
427 }
428 }
430 #=== FUNCTION ================================================================
431 # NAME: import_modules
432 # PARAMETERS: module_path - string - abs. path to the directory the modules
433 # are stored
434 # RETURNS: nothing
435 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
436 # state is on is imported by "require 'file';"
437 #===============================================================================
438 sub import_modules {
439 daemon_log(" ", 1);
441 if (not -e $modules_path) {
442 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
443 }
445 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
446 while (defined (my $file = readdir (DIR))) {
447 if (not $file =~ /(\S*?).pm$/) {
448 next;
449 }
450 my $mod_name = $1;
452 if( $file =~ /ArpHandler.pm/ ) {
453 if( $no_arp > 0 ) {
454 next;
455 }
456 }
458 eval { require $file; };
459 if ($@) {
460 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
461 daemon_log("$@", 5);
462 } else {
463 my $info = eval($mod_name.'::get_module_info()');
464 # Only load module if get_module_info() returns a non-null object
465 if( $info ) {
466 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
467 $known_modules->{$mod_name} = $info;
468 daemon_log("0 INFO: module $mod_name loaded", 5);
469 }
470 }
471 }
472 close (DIR);
473 }
476 #=== FUNCTION ================================================================
477 # NAME: sig_int_handler
478 # PARAMETERS: signal - string - signal arose from system
479 # RETURNS: noting
480 # DESCRIPTION: handels tasks to be done befor signal becomes active
481 #===============================================================================
482 sub sig_int_handler {
483 my ($signal) = @_;
485 # if (defined($ldap_handle)) {
486 # $ldap_handle->disconnect;
487 # }
488 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
491 daemon_log("shutting down gosa-si-server", 1);
492 system("kill `ps -C gosa-si-server -o pid=`");
493 }
494 $SIG{INT} = \&sig_int_handler;
497 sub check_key_and_xml_validity {
498 my ($crypted_msg, $module_key, $session_id) = @_;
499 my $msg;
500 my $msg_hash;
501 my $error_string;
502 eval{
503 $msg = &decrypt_msg($crypted_msg, $module_key);
505 if ($msg =~ /<xml>/i){
506 $msg =~ s/\s+/ /g; # just for better daemon_log
507 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
508 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
510 ##############
511 # check header
512 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
513 my $header_l = $msg_hash->{'header'};
514 if( 1 > @{$header_l} ) { die 'empty header tag'; }
515 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
516 my $header = @{$header_l}[0];
517 if( 0 == length $header) { die 'empty string in header tag'; }
519 ##############
520 # check source
521 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
522 my $source_l = $msg_hash->{'source'};
523 if( 1 > @{$source_l} ) { die 'empty source tag'; }
524 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
525 my $source = @{$source_l}[0];
526 if( 0 == length $source) { die 'source error'; }
528 ##############
529 # check target
530 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
531 my $target_l = $msg_hash->{'target'};
532 if( 1 > @{$target_l} ) { die 'empty target tag'; }
533 }
534 };
535 if($@) {
536 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
537 $msg = undef;
538 $msg_hash = undef;
539 }
541 return ($msg, $msg_hash);
542 }
545 sub check_outgoing_xml_validity {
546 my ($msg, $session_id) = @_;
548 my $msg_hash;
549 eval{
550 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
552 ##############
553 # check header
554 my $header_l = $msg_hash->{'header'};
555 if( 1 != @{$header_l} ) {
556 die 'no or more than one headers specified';
557 }
558 my $header = @{$header_l}[0];
559 if( 0 == length $header) {
560 die 'header has length 0';
561 }
563 ##############
564 # check source
565 my $source_l = $msg_hash->{'source'};
566 if( 1 != @{$source_l} ) {
567 die 'no or more than 1 sources specified';
568 }
569 my $source = @{$source_l}[0];
570 if( 0 == length $source) {
571 die 'source has length 0';
572 }
573 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
574 $source =~ /^GOSA$/i ) {
575 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
576 }
578 ##############
579 # check target
580 my $target_l = $msg_hash->{'target'};
581 if( 0 == @{$target_l} ) {
582 die "no targets specified";
583 }
584 foreach my $target (@$target_l) {
585 if( 0 == length $target) {
586 die "target has length 0";
587 }
588 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
589 $target =~ /^GOSA$/i ||
590 $target =~ /^\*$/ ||
591 $target =~ /KNOWN_SERVER/i ||
592 $target =~ /JOBDB/i ||
593 $target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ){
594 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
595 }
596 }
597 };
598 if($@) {
599 daemon_log("$session_id WARNING: outgoing msg is not gosa-si envelope conform: ", 5);
600 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 5);
601 $msg_hash = undef;
602 }
604 return ($msg_hash);
605 }
608 sub input_from_known_server {
609 my ($input, $remote_ip, $session_id) = @_ ;
610 my ($msg, $msg_hash, $module);
612 my $sql_statement= "SELECT * FROM known_server";
613 my $query_res = $known_server_db->select_dbentry( $sql_statement );
615 while( my ($hit_num, $hit) = each %{ $query_res } ) {
616 my $host_name = $hit->{hostname};
617 if( not $host_name =~ "^$remote_ip") {
618 next;
619 }
620 my $host_key = $hit->{hostkey};
621 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
622 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
624 # check if module can open msg envelope with module key
625 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
626 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
627 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
628 daemon_log("$@", 8);
629 next;
630 }
631 else {
632 $msg = $tmp_msg;
633 $msg_hash = $tmp_msg_hash;
634 $module = "ServerPackages";
635 last;
636 }
637 }
639 if( (!$msg) || (!$msg_hash) || (!$module) ) {
640 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
641 }
643 return ($msg, $msg_hash, $module);
644 }
647 sub input_from_known_client {
648 my ($input, $remote_ip, $session_id) = @_ ;
649 my ($msg, $msg_hash, $module);
651 my $sql_statement= "SELECT * FROM known_clients";
652 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
653 while( my ($hit_num, $hit) = each %{ $query_res } ) {
654 my $host_name = $hit->{hostname};
655 if( not $host_name =~ /^$remote_ip:\d*$/) {
656 next;
657 }
658 my $host_key = $hit->{hostkey};
659 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
660 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
662 # check if module can open msg envelope with module key
663 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
665 if( (!$msg) || (!$msg_hash) ) {
666 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
667 &daemon_log("$@", 8);
668 next;
669 }
670 else {
671 $module = "ClientPackages";
672 last;
673 }
674 }
676 if( (!$msg) || (!$msg_hash) || (!$module) ) {
677 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
678 }
680 return ($msg, $msg_hash, $module);
681 }
684 sub input_from_unknown_host {
685 no strict "refs";
686 my ($input, $session_id) = @_ ;
687 my ($msg, $msg_hash, $module);
688 my $error_string;
690 my %act_modules = %$known_modules;
692 while( my ($mod, $info) = each(%act_modules)) {
694 # check a key exists for this module
695 my $module_key = ${$mod."_key"};
696 if( not defined $module_key ) {
697 if( $mod eq 'ArpHandler' ) {
698 next;
699 }
700 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
701 next;
702 }
703 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
705 # check if module can open msg envelope with module key
706 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
707 if( (not defined $msg) || (not defined $msg_hash) ) {
708 next;
709 }
710 else {
711 $module = $mod;
712 last;
713 }
714 }
716 if( (!$msg) || (!$msg_hash) || (!$module)) {
717 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
718 }
720 return ($msg, $msg_hash, $module);
721 }
724 sub create_ciphering {
725 my ($passwd) = @_;
726 if((!defined($passwd)) || length($passwd)==0) {
727 $passwd = "";
728 }
729 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
730 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
731 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
732 $my_cipher->set_iv($iv);
733 return $my_cipher;
734 }
737 sub encrypt_msg {
738 my ($msg, $key) = @_;
739 my $my_cipher = &create_ciphering($key);
740 my $len;
741 {
742 use bytes;
743 $len= 16-length($msg)%16;
744 }
745 $msg = "\0"x($len).$msg;
746 $msg = $my_cipher->encrypt($msg);
747 chomp($msg = &encode_base64($msg));
748 # there are no newlines allowed inside msg
749 $msg=~ s/\n//g;
750 return $msg;
751 }
754 sub decrypt_msg {
756 my ($msg, $key) = @_ ;
757 $msg = &decode_base64($msg);
758 my $my_cipher = &create_ciphering($key);
759 $msg = $my_cipher->decrypt($msg);
760 $msg =~ s/\0*//g;
761 return $msg;
762 }
765 sub get_encrypt_key {
766 my ($target) = @_ ;
767 my $encrypt_key;
768 my $error = 0;
770 # target can be in known_server
771 if( not defined $encrypt_key ) {
772 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
773 my $query_res = $known_server_db->select_dbentry( $sql_statement );
774 while( my ($hit_num, $hit) = each %{ $query_res } ) {
775 my $host_name = $hit->{hostname};
776 if( $host_name ne $target ) {
777 next;
778 }
779 $encrypt_key = $hit->{hostkey};
780 last;
781 }
782 }
784 # target can be in known_client
785 if( not defined $encrypt_key ) {
786 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
787 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
788 while( my ($hit_num, $hit) = each %{ $query_res } ) {
789 my $host_name = $hit->{hostname};
790 if( $host_name ne $target ) {
791 next;
792 }
793 $encrypt_key = $hit->{hostkey};
794 last;
795 }
796 }
798 return $encrypt_key;
799 }
802 #=== FUNCTION ================================================================
803 # NAME: open_socket
804 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
805 # [PeerPort] string necessary if port not appended by PeerAddr
806 # RETURNS: socket IO::Socket::INET
807 # DESCRIPTION: open a socket to PeerAddr
808 #===============================================================================
809 sub open_socket {
810 my ($PeerAddr, $PeerPort) = @_ ;
811 if(defined($PeerPort)){
812 $PeerAddr = $PeerAddr.":".$PeerPort;
813 }
814 my $socket;
815 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
816 Porto => "tcp",
817 Type => SOCK_STREAM,
818 Timeout => 5,
819 );
820 if(not defined $socket) {
821 return;
822 }
823 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
824 return $socket;
825 }
828 sub get_local_ip_for_remote_ip {
829 my $remote_ip= shift;
830 my $result="0.0.0.0";
832 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
833 if($remote_ip eq "127.0.0.1") {
834 $result = "127.0.0.1";
835 } else {
836 my $PROC_NET_ROUTE= ('/proc/net/route');
838 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
839 or die "Could not open $PROC_NET_ROUTE";
841 my @ifs = <PROC_NET_ROUTE>;
843 close(PROC_NET_ROUTE);
845 # Eat header line
846 shift @ifs;
847 chomp @ifs;
848 foreach my $line(@ifs) {
849 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
850 my $destination;
851 my $mask;
852 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
853 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
854 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
855 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
856 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
857 # destination matches route, save mac and exit
858 $result= &get_ip($Iface);
859 last;
860 }
861 }
862 }
863 } else {
864 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
865 }
866 return $result;
867 }
870 sub send_msg_to_target {
871 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
872 my $error = 0;
873 my $header;
874 my $timestamp = &get_time();
875 my $new_status;
876 my $act_status;
877 my ($sql_statement, $res);
879 if( $msg_header ) {
880 $header = "'$msg_header'-";
881 } else {
882 $header = "";
883 }
885 # Patch the source ip
886 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
887 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
888 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
889 }
891 # encrypt xml msg
892 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
894 # opensocket
895 my $socket = &open_socket($address);
896 if( !$socket ) {
897 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
898 $error++;
899 }
901 if( $error == 0 ) {
902 # send xml msg
903 print $socket $crypted_msg."\n";
905 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
906 daemon_log("$session_id DEBUG: message:\n$msg", 9);
908 }
910 # close socket in any case
911 if( $socket ) {
912 close $socket;
913 }
915 if( $error > 0 ) { $new_status = "down"; }
916 else { $new_status = $msg_header; }
919 # known_clients
920 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
921 $res = $known_clients_db->select_dbentry($sql_statement);
922 if( keys(%$res) == 1) {
923 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
924 if ($act_status eq "down" && $new_status eq "down") {
925 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
926 $res = $known_clients_db->del_dbentry($sql_statement);
927 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
928 } else {
929 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
930 $res = $known_clients_db->update_dbentry($sql_statement);
931 if($new_status eq "down"){
932 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
933 } else {
934 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
935 }
936 }
937 }
939 # known_server
940 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
941 $res = $known_server_db->select_dbentry($sql_statement);
942 if( keys(%$res) == 1) {
943 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
944 if ($act_status eq "down" && $new_status eq "down") {
945 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
946 $res = $known_server_db->del_dbentry($sql_statement);
947 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
948 }
949 else {
950 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
951 $res = $known_server_db->update_dbentry($sql_statement);
952 if($new_status eq "down"){
953 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
954 } else {
955 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
956 }
957 }
958 }
959 return $error;
960 }
963 sub update_jobdb_status_for_send_msgs {
964 my ($answer, $error) = @_;
965 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
966 my $jobdb_id = $1;
968 # sending msg faild
969 if( $error ) {
970 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
971 my $sql_statement = "UPDATE $job_queue_tn ".
972 "SET status='error', result='can not deliver msg, please consult log file' ".
973 "WHERE id=$jobdb_id";
974 my $res = $job_db->update_dbentry($sql_statement);
975 }
977 # sending msg was successful
978 } else {
979 my $sql_statement = "UPDATE $job_queue_tn ".
980 "SET status='done' ".
981 "WHERE id=$jobdb_id AND status='processed'";
982 my $res = $job_db->update_dbentry($sql_statement);
983 }
984 }
985 }
988 sub sig_handler {
989 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
990 daemon_log("0 INFO got signal '$signal'", 1);
991 $kernel->sig_handled();
992 return;
993 }
996 sub msg_to_decrypt {
997 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
998 my $session_id = $session->ID;
999 my ($msg, $msg_hash, $module);
1000 my $error = 0;
1002 # hole neue msg aus @msgs_to_decrypt
1003 my $next_msg = shift @msgs_to_decrypt;
1005 # entschlüssle sie
1007 # msg is from a new client or gosa
1008 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1009 # msg is from a gosa-si-server
1010 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1011 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1012 }
1013 # msg is from a gosa-si-client
1014 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1015 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1016 }
1017 # an error occurred
1018 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1019 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1020 # could not understand a msg from its server the client cause a re-registering process
1021 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1022 "' to cause a re-registering of the client if necessary", 5);
1023 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1024 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1025 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1026 my $host_name = $hit->{'hostname'};
1027 my $host_key = $hit->{'hostkey'};
1028 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1029 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1030 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1031 }
1032 $error++;
1033 }
1036 my $header;
1037 my $target;
1038 my $source;
1039 my $done = 0;
1040 my $sql;
1041 my $res;
1043 # check whether this message should be processed here
1044 if ($error == 0) {
1045 $header = @{$msg_hash->{'header'}}[0];
1046 $target = @{$msg_hash->{'target'}}[0];
1047 $source = @{$msg_hash->{'source'}}[0];
1048 my $not_found_in_known_clients_db = 0;
1049 my $not_found_in_known_server_db = 0;
1050 my $not_found_in_foreign_clients_db = 0;
1051 my $local_address;
1052 my ($target_ip, $target_port) = split(':', $target);
1053 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1054 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1055 } else {
1056 $local_address = $server_address;
1057 }
1059 # target and source is equal to GOSA -> process here
1060 if (not $done) {
1061 if ($target eq "GOSA" && $source eq "GOSA") {
1062 $done = 1;
1063 }
1064 }
1066 # target is own address without forward_to_gosa-tag -> process here
1067 if (not $done) {
1068 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1069 $done = 1;
1070 if ($source eq "GOSA") {
1071 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1072 }
1073 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1074 }
1075 }
1077 # target is a client address in known_clients -> process here
1078 if (not $done) {
1079 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1080 $res = $known_clients_db->select_dbentry($sql);
1081 if (keys(%$res) > 0) {
1082 $done = 1;
1083 my $hostname = $res->{1}->{'hostname'};
1084 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1085 #print STDERR "target is a client address in known_clients -> process here\n";
1086 } else {
1087 $not_found_in_known_clients_db = 1;
1088 }
1089 }
1091 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1092 if (not $done) {
1093 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1094 my $gosa_at;
1095 my $gosa_session_id;
1096 if (($target eq $local_address) && (defined $forward_to_gosa)){
1097 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1098 if ($gosa_at ne $local_address) {
1099 $done = 1;
1100 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1101 }
1102 }
1103 }
1105 # if message should be processed here -> add message to incoming_db
1106 if ($done) {
1107 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1108 # so gosa-si-server knows how to process this kind of messages
1109 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1110 $module = "GosaPackages";
1111 }
1113 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1114 primkey=>[],
1115 headertag=>$header,
1116 targettag=>$target,
1117 xmlmessage=>&encode_base64($msg),
1118 timestamp=>&get_time,
1119 module=>$module,
1120 sessionid=>$session_id,
1121 } );
1122 }
1124 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1125 if (not $done) {
1126 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1127 my $gosa_at;
1128 my $gosa_session_id;
1129 if (($target eq $local_address) && (defined $forward_to_gosa)){
1130 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1131 if ($gosa_at eq $local_address) {
1132 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1133 if( defined $session_reference ) {
1134 $heap = $session_reference->get_heap();
1135 }
1136 if(exists $heap->{'client'}) {
1137 $msg = &encrypt_msg($msg, $GosaPackages_key);
1138 $heap->{'client'}->put($msg);
1139 }
1140 $done = 1;
1141 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1142 }
1143 }
1145 }
1147 # target is a client address in foreign_clients -> forward to registration server
1148 if (not $done) {
1149 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1150 $res = $foreign_clients_db->select_dbentry($sql);
1151 if (keys(%$res) > 0) {
1152 my $hostname = $res->{1}->{'hostname'};
1153 my ($host_ip, $host_port) = split(/:/, $hostname);
1154 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1155 my $regserver = $res->{1}->{'regserver'};
1156 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1157 my $res = $known_server_db->select_dbentry($sql);
1158 if (keys(%$res) > 0) {
1159 my $regserver_key = $res->{1}->{'hostkey'};
1160 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1161 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1162 if ($source eq "GOSA") {
1163 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1164 }
1165 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1166 }
1167 $done = 1;
1168 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1169 } else {
1170 $not_found_in_foreign_clients_db = 1;
1171 }
1172 }
1174 # target is a server address -> forward to server
1175 if (not $done) {
1176 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1177 $res = $known_server_db->select_dbentry($sql);
1178 if (keys(%$res) > 0) {
1179 my $hostkey = $res->{1}->{'hostkey'};
1181 if ($source eq "GOSA") {
1182 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1183 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1185 }
1187 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1188 $done = 1;
1189 #print STDERR "target is a server address -> forward to server\n";
1190 } else {
1191 $not_found_in_known_server_db = 1;
1192 }
1193 }
1196 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1197 if ( $not_found_in_foreign_clients_db
1198 && $not_found_in_known_server_db
1199 && $not_found_in_known_clients_db) {
1200 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1201 primkey=>[],
1202 headertag=>$header,
1203 targettag=>$target,
1204 xmlmessage=>&encode_base64($msg),
1205 timestamp=>&get_time,
1206 module=>$module,
1207 sessionid=>$session_id,
1208 } );
1209 $done = 1;
1210 }
1213 if (not $done) {
1214 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1215 if ($source eq "GOSA") {
1216 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1217 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1219 my $session_reference = $kernel->ID_id_to_session($session_id);
1220 if( defined $session_reference ) {
1221 $heap = $session_reference->get_heap();
1222 }
1223 if(exists $heap->{'client'}) {
1224 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1225 $heap->{'client'}->put($error_msg);
1226 }
1227 }
1228 }
1230 }
1232 return;
1233 }
1236 sub next_task {
1237 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1238 my $running_task = POE::Wheel::Run->new(
1239 Program => sub { process_task($session, $heap, $task) },
1240 StdioFilter => POE::Filter::Reference->new(),
1241 StdoutEvent => "task_result",
1242 StderrEvent => "task_debug",
1243 CloseEvent => "task_done",
1244 );
1245 $heap->{task}->{ $running_task->ID } = $running_task;
1246 }
1248 sub handle_task_result {
1249 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1250 my $client_answer = $result->{'answer'};
1251 if( $client_answer =~ s/session_id=(\d+)$// ) {
1252 my $session_id = $1;
1253 if( defined $session_id ) {
1254 my $session_reference = $kernel->ID_id_to_session($session_id);
1255 if( defined $session_reference ) {
1256 $heap = $session_reference->get_heap();
1257 }
1258 }
1260 if(exists $heap->{'client'}) {
1261 $heap->{'client'}->put($client_answer);
1262 }
1263 }
1264 $kernel->sig(CHLD => "child_reap");
1265 }
1267 sub handle_task_debug {
1268 my $result = $_[ARG0];
1269 print STDERR "$result\n";
1270 }
1272 sub handle_task_done {
1273 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1274 delete $heap->{task}->{$task_id};
1275 }
1277 sub process_task {
1278 no strict "refs";
1279 #CHECK: Not @_[...]?
1280 my ($session, $heap, $task) = @_;
1281 my $error = 0;
1282 my $answer_l;
1283 my ($answer_header, @answer_target_l, $answer_source);
1284 my $client_answer = "";
1286 # prepare all variables needed to process message
1287 #my $msg = $task->{'xmlmessage'};
1288 my $msg = &decode_base64($task->{'xmlmessage'});
1289 my $incoming_id = $task->{'id'};
1290 my $module = $task->{'module'};
1291 my $header = $task->{'headertag'};
1292 my $session_id = $task->{'sessionid'};
1293 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1294 my $source = @{$msg_hash->{'source'}}[0];
1296 # set timestamp of incoming client uptodate, so client will not
1297 # be deleted from known_clients because of expiration
1298 my $act_time = &get_time();
1299 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1300 my $res = $known_clients_db->exec_statement($sql);
1302 ######################
1303 # process incoming msg
1304 if( $error == 0) {
1305 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1306 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1307 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1309 if ( 0 < @{$answer_l} ) {
1310 my $answer_str = join("\n", @{$answer_l});
1311 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1312 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1313 }
1314 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1315 } else {
1316 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1317 }
1319 }
1320 if( !$answer_l ) { $error++ };
1322 ########
1323 # answer
1324 if( $error == 0 ) {
1326 foreach my $answer ( @{$answer_l} ) {
1327 # check outgoing msg to xml validity
1328 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1329 if( not defined $answer_hash ) { next; }
1331 $answer_header = @{$answer_hash->{'header'}}[0];
1332 @answer_target_l = @{$answer_hash->{'target'}};
1333 $answer_source = @{$answer_hash->{'source'}}[0];
1335 # deliver msg to all targets
1336 foreach my $answer_target ( @answer_target_l ) {
1338 # targets of msg are all gosa-si-clients in known_clients_db
1339 if( $answer_target eq "*" ) {
1340 # answer is for all clients
1341 my $sql_statement= "SELECT * FROM known_clients";
1342 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1343 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1344 my $host_name = $hit->{hostname};
1345 my $host_key = $hit->{hostkey};
1346 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1347 &update_jobdb_status_for_send_msgs($answer, $error);
1348 }
1349 }
1351 # targets of msg are all gosa-si-server in known_server_db
1352 elsif( $answer_target eq "KNOWN_SERVER" ) {
1353 # answer is for all server in known_server
1354 my $sql_statement= "SELECT * FROM $known_server_tn";
1355 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1356 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1357 my $host_name = $hit->{hostname};
1358 my $host_key = $hit->{hostkey};
1359 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1360 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1361 &update_jobdb_status_for_send_msgs($answer, $error);
1362 }
1363 }
1365 # target of msg is GOsa
1366 elsif( $answer_target eq "GOSA" ) {
1367 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1368 my $add_on = "";
1369 if( defined $session_id ) {
1370 $add_on = ".session_id=$session_id";
1371 }
1372 # answer is for GOSA and has to returned to connected client
1373 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1374 $client_answer = $gosa_answer.$add_on;
1375 }
1377 # target of msg is job queue at this host
1378 elsif( $answer_target eq "JOBDB") {
1379 $answer =~ /<header>(\S+)<\/header>/;
1380 my $header;
1381 if( defined $1 ) { $header = $1; }
1382 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1383 &update_jobdb_status_for_send_msgs($answer, $error);
1384 }
1386 # target of msg is a mac address
1387 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 ) {
1388 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1389 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1390 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1391 my $found_ip_flag = 0;
1392 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1393 my $host_name = $hit->{hostname};
1394 my $host_key = $hit->{hostkey};
1395 $answer =~ s/$answer_target/$host_name/g;
1396 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1397 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1398 &update_jobdb_status_for_send_msgs($answer, $error);
1399 $found_ip_flag++ ;
1400 }
1401 if( $found_ip_flag == 0) {
1402 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1403 }
1405 # answer is for one specific host
1406 } else {
1407 # get encrypt_key
1408 my $encrypt_key = &get_encrypt_key($answer_target);
1409 if( not defined $encrypt_key ) {
1410 # unknown target
1411 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1412 next;
1413 }
1414 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1415 &update_jobdb_status_for_send_msgs($answer, $error);
1416 }
1417 }
1418 }
1419 }
1421 my $filter = POE::Filter::Reference->new();
1422 my %result = (
1423 status => "seems ok to me",
1424 answer => $client_answer,
1425 );
1427 my $output = $filter->put( [ \%result ] );
1428 print @$output;
1431 }
1433 sub session_start {
1434 my ($kernel) = $_[KERNEL];
1435 $global_kernel = $kernel;
1436 $kernel->yield('register_at_foreign_servers');
1437 $kernel->yield('create_fai_server_db', $fai_server_tn );
1438 $kernel->yield('create_fai_release_db', $fai_release_tn );
1439 $kernel->yield('watch_for_next_tasks');
1440 $kernel->sig(USR1 => "sig_handler");
1441 $kernel->sig(USR2 => "recreate_packages_db");
1442 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1443 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1444 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1445 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1446 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1447 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1448 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1451 }
1454 sub watch_for_done_jobs {
1455 #CHECK: $heap for what?
1456 my ($kernel,$heap) = @_[KERNEL, HEAP];
1458 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1459 my $res = $job_db->select_dbentry( $sql_statement );
1461 while( my ($id, $hit) = each %{$res} ) {
1462 my $jobdb_id = $hit->{id};
1463 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1464 my $res = $job_db->del_dbentry($sql_statement);
1465 }
1467 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1468 }
1471 # if a job got an update or was modified anyway, send to all other si-server an update message
1472 # of this jobs
1473 sub watch_for_modified_jobs {
1474 my ($kernel,$heap) = @_[KERNEL, HEAP];
1476 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1477 my $res = $job_db->select_dbentry( $sql_statement );
1479 # if db contains no jobs which should be update, do nothing
1480 if (keys %$res != 0) {
1482 if ($job_synchronization eq "true") {
1483 # make out of the db result a gosa-si message
1484 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1486 # update all other SI-server
1487 &inform_all_other_si_server($update_msg);
1488 }
1490 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1491 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1492 $res = $job_db->update_dbentry($sql_statement);
1493 }
1495 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1496 }
1499 sub watch_for_new_jobs {
1500 if($watch_for_new_jobs_in_progress == 0) {
1501 $watch_for_new_jobs_in_progress = 1;
1502 my ($kernel,$heap) = @_[KERNEL, HEAP];
1504 # check gosa job quaeue for jobs with executable timestamp
1505 my $timestamp = &get_time();
1506 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1507 my $res = $job_db->exec_statement( $sql_statement );
1509 # Merge all new jobs that would do the same actions
1510 my @drops;
1511 my $hits;
1512 foreach my $hit (reverse @{$res} ) {
1513 my $macaddress= lc @{$hit}[8];
1514 my $headertag= @{$hit}[5];
1515 if(
1516 defined($hits->{$macaddress}) &&
1517 defined($hits->{$macaddress}->{$headertag}) &&
1518 defined($hits->{$macaddress}->{$headertag}[0])
1519 ) {
1520 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1521 }
1522 $hits->{$macaddress}->{$headertag}= $hit;
1523 }
1525 # Delete new jobs with a matching job in state 'processing'
1526 foreach my $macaddress (keys %{$hits}) {
1527 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1528 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1529 if(defined($jobdb_id)) {
1530 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1531 my $res = $job_db->exec_statement( $sql_statement );
1532 foreach my $hit (@{$res}) {
1533 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1534 }
1535 } else {
1536 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1537 }
1538 }
1539 }
1541 # Commit deletion
1542 $job_db->exec_statementlist(\@drops);
1544 # Look for new jobs that could be executed
1545 foreach my $macaddress (keys %{$hits}) {
1547 # Look if there is an executing job
1548 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1549 my $res = $job_db->exec_statement( $sql_statement );
1551 # Skip new jobs for host if there is a processing job
1552 if(defined($res) and defined @{$res}[0]) {
1553 next;
1554 }
1556 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1557 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1558 if(defined($jobdb_id)) {
1559 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1561 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1562 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1563 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1565 # expect macaddress is unique!!!!!!
1566 my $target = $res_hash->{1}->{hostname};
1568 # change header
1569 $job_msg =~ s/<header>job_/<header>gosa_/;
1571 # add sqlite_id
1572 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1574 $job_msg =~ /<header>(\S+)<\/header>/;
1575 my $header = $1 ;
1576 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1578 # update status in job queue to 'processing'
1579 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1580 my $res = $job_db->update_dbentry($sql_statement);
1581 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1583 # We don't want parallel processing
1584 last;
1585 }
1586 }
1587 }
1589 $watch_for_new_jobs_in_progress = 0;
1590 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1591 }
1592 }
1596 sub watch_for_new_messages {
1597 my ($kernel,$heap) = @_[KERNEL, HEAP];
1598 my @coll_user_msg; # collection list of outgoing messages
1600 # check messaging_db for new incoming messages with executable timestamp
1601 my $timestamp = &get_time();
1602 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1603 my $res = $messaging_db->exec_statement( $sql_statement );
1604 foreach my $hit (@{$res}) {
1606 # create outgoing messages
1607 my $message_to = @{$hit}[3];
1608 # translate message_to to plain login name
1609 my @message_to_l = split(/,/, $message_to);
1610 my %receiver_h;
1611 foreach my $receiver (@message_to_l) {
1612 if ($receiver =~ /^u_([\s\S]*)$/) {
1613 $receiver_h{$1} = 0;
1614 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1615 my $group_name = $1;
1616 # fetch all group members from ldap and add them to receiver hash
1617 my $ldap_handle = &get_ldap_handle();
1618 if (defined $ldap_handle) {
1619 my $mesg = $ldap_handle->search(
1620 base => $ldap_base,
1621 scope => 'sub',
1622 attrs => ['memberUid'],
1623 filter => "cn=$group_name",
1624 );
1625 if ($mesg->count) {
1626 my @entries = $mesg->entries;
1627 foreach my $entry (@entries) {
1628 my @receivers= $entry->get_value("memberUid");
1629 foreach my $receiver (@receivers) {
1630 $receiver_h{$1} = 0;
1631 }
1632 }
1633 }
1634 # translating errors ?
1635 if ($mesg->code) {
1636 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1637 }
1638 # ldap handle error ?
1639 } else {
1640 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1641 }
1642 } else {
1643 my $sbjct = &encode_base64(@{$hit}[1]);
1644 my $msg = &encode_base64(@{$hit}[7]);
1645 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1646 }
1647 }
1648 my @receiver_l = keys(%receiver_h);
1650 my $message_id = @{$hit}[0];
1652 #add each outgoing msg to messaging_db
1653 my $receiver;
1654 foreach $receiver (@receiver_l) {
1655 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1656 "VALUES ('".
1657 $message_id."', '". # id
1658 @{$hit}[1]."', '". # subject
1659 @{$hit}[2]."', '". # message_from
1660 $receiver."', '". # message_to
1661 "none"."', '". # flag
1662 "out"."', '". # direction
1663 @{$hit}[6]."', '". # delivery_time
1664 @{$hit}[7]."', '". # message
1665 $timestamp."'". # timestamp
1666 ")";
1667 &daemon_log("M DEBUG: $sql_statement", 1);
1668 my $res = $messaging_db->exec_statement($sql_statement);
1669 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1670 }
1672 # set incoming message to flag d=deliverd
1673 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1674 &daemon_log("M DEBUG: $sql_statement", 7);
1675 $res = $messaging_db->update_dbentry($sql_statement);
1676 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1677 }
1679 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1680 return;
1681 }
1683 sub watch_for_delivery_messages {
1684 my ($kernel, $heap) = @_[KERNEL, HEAP];
1686 # select outgoing messages
1687 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1688 #&daemon_log("0 DEBUG: $sql", 7);
1689 my $res = $messaging_db->exec_statement( $sql_statement );
1691 # build out msg for each usr
1692 foreach my $hit (@{$res}) {
1693 my $receiver = @{$hit}[3];
1694 my $msg_id = @{$hit}[0];
1695 my $subject = @{$hit}[1];
1696 my $message = @{$hit}[7];
1698 # resolve usr -> host where usr is logged in
1699 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1700 #&daemon_log("0 DEBUG: $sql", 7);
1701 my $res = $login_users_db->exec_statement($sql);
1703 # reciver is logged in nowhere
1704 if (not ref(@$res[0]) eq "ARRAY") { next; }
1706 my $send_succeed = 0;
1707 foreach my $hit (@$res) {
1708 my $receiver_host = @$hit[0];
1709 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1711 # fetch key to encrypt msg propperly for usr/host
1712 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1713 &daemon_log("0 DEBUG: $sql", 7);
1714 my $res = $known_clients_db->exec_statement($sql);
1716 # host is already down
1717 if (not ref(@$res[0]) eq "ARRAY") { next; }
1719 # host is on
1720 my $receiver_key = @{@{$res}[0]}[2];
1721 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1722 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1723 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1724 if ($error == 0 ) {
1725 $send_succeed++ ;
1726 }
1727 }
1729 if ($send_succeed) {
1730 # set outgoing msg at db to deliverd
1731 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1732 &daemon_log("0 DEBUG: $sql", 7);
1733 my $res = $messaging_db->exec_statement($sql);
1734 }
1735 }
1737 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1738 return;
1739 }
1742 sub watch_for_done_messages {
1743 my ($kernel,$heap) = @_[KERNEL, HEAP];
1745 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1746 #&daemon_log("0 DEBUG: $sql", 7);
1747 my $res = $messaging_db->exec_statement($sql);
1749 foreach my $hit (@{$res}) {
1750 my $msg_id = @{$hit}[0];
1752 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1753 #&daemon_log("0 DEBUG: $sql", 7);
1754 my $res = $messaging_db->exec_statement($sql);
1756 # not all usr msgs have been seen till now
1757 if ( ref(@$res[0]) eq "ARRAY") { next; }
1759 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1760 #&daemon_log("0 DEBUG: $sql", 7);
1761 $res = $messaging_db->exec_statement($sql);
1763 }
1765 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1766 return;
1767 }
1770 sub watch_for_old_known_clients {
1771 my ($kernel,$heap) = @_[KERNEL, HEAP];
1773 my $sql_statement = "SELECT * FROM $known_clients_tn";
1774 my $res = $known_clients_db->select_dbentry( $sql_statement );
1776 my $act_time = int(&get_time());
1778 while ( my ($hit_num, $hit) = each %$res) {
1779 my $expired_timestamp = int($hit->{'timestamp'});
1780 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1781 my $dt = DateTime->new( year => $1,
1782 month => $2,
1783 day => $3,
1784 hour => $4,
1785 minute => $5,
1786 second => $6,
1787 );
1789 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1790 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1791 if ($act_time > $expired_timestamp) {
1792 my $hostname = $hit->{'hostname'};
1793 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1794 my $del_res = $known_clients_db->exec_statement($del_sql);
1796 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1797 }
1799 }
1801 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1802 }
1805 sub watch_for_next_tasks {
1806 my ($kernel,$heap) = @_[KERNEL, HEAP];
1808 my $sql = "SELECT * FROM $incoming_tn";
1809 my $res = $incoming_db->select_dbentry($sql);
1811 while ( my ($hit_num, $hit) = each %$res) {
1812 my $headertag = $hit->{'headertag'};
1813 if ($headertag =~ /^answer_(\d+)/) {
1814 # do not start processing, this message is for a still running POE::Wheel
1815 next;
1816 }
1817 my $message_id = $hit->{'id'};
1818 $kernel->yield('next_task', $hit);
1820 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1821 my $res = $incoming_db->exec_statement($sql);
1822 }
1824 $kernel->delay_set('watch_for_next_tasks', 0.1);
1825 }
1828 sub get_ldap_handle {
1829 my ($session_id) = @_;
1830 my $heap;
1831 my $ldap_handle;
1833 if (not defined $session_id ) { $session_id = 0 };
1834 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1836 if ($session_id == 0) {
1837 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1838 $ldap_handle = Net::LDAP->new( $ldap_uri );
1839 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1841 } else {
1842 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1843 if( defined $session_reference ) {
1844 $heap = $session_reference->get_heap();
1845 }
1847 if (not defined $heap) {
1848 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1849 return;
1850 }
1852 # TODO: This "if" is nonsense, because it doesn't prove that the
1853 # used handle is still valid - or if we've to reconnect...
1854 #if (not exists $heap->{ldap_handle}) {
1855 $ldap_handle = Net::LDAP->new( $ldap_uri );
1856 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
1857 $heap->{ldap_handle} = $ldap_handle;
1858 #}
1859 }
1860 return $ldap_handle;
1861 }
1864 sub change_fai_state {
1865 my ($st, $targets, $session_id) = @_;
1866 $session_id = 0 if not defined $session_id;
1867 # Set FAI state to localboot
1868 my %mapActions= (
1869 reboot => '',
1870 update => 'softupdate',
1871 localboot => 'localboot',
1872 reinstall => 'install',
1873 rescan => '',
1874 wake => '',
1875 memcheck => 'memcheck',
1876 sysinfo => 'sysinfo',
1877 install => 'install',
1878 );
1880 # Return if this is unknown
1881 if (!exists $mapActions{ $st }){
1882 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1883 return;
1884 }
1886 my $state= $mapActions{ $st };
1888 my $ldap_handle = &get_ldap_handle($session_id);
1889 if( defined($ldap_handle) ) {
1891 # Build search filter for hosts
1892 my $search= "(&(objectClass=GOhard)";
1893 foreach (@{$targets}){
1894 $search.= "(macAddress=$_)";
1895 }
1896 $search.= ")";
1898 # If there's any host inside of the search string, procress them
1899 if (!($search =~ /macAddress/)){
1900 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1901 return;
1902 }
1904 # Perform search for Unit Tag
1905 my $mesg = $ldap_handle->search(
1906 base => $ldap_base,
1907 scope => 'sub',
1908 attrs => ['dn', 'FAIstate', 'objectClass'],
1909 filter => "$search"
1910 );
1912 if ($mesg->count) {
1913 my @entries = $mesg->entries;
1914 if (0 == @entries) {
1915 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1916 }
1918 foreach my $entry (@entries) {
1919 # Only modify entry if it is not set to '$state'
1920 if ($entry->get_value("FAIstate") ne "$state"){
1921 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1922 my $result;
1923 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1924 if (exists $tmp{'FAIobject'}){
1925 if ($state eq ''){
1926 $result= $ldap_handle->modify($entry->dn, changes => [
1927 delete => [ FAIstate => [] ] ]);
1928 } else {
1929 $result= $ldap_handle->modify($entry->dn, changes => [
1930 replace => [ FAIstate => $state ] ]);
1931 }
1932 } elsif ($state ne ''){
1933 $result= $ldap_handle->modify($entry->dn, changes => [
1934 add => [ objectClass => 'FAIobject' ],
1935 add => [ FAIstate => $state ] ]);
1936 }
1938 # Errors?
1939 if ($result->code){
1940 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1941 }
1942 } else {
1943 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1944 }
1945 }
1946 } else {
1947 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1948 }
1950 # if no ldap handle defined
1951 } else {
1952 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1953 }
1955 return;
1956 }
1959 sub change_goto_state {
1960 my ($st, $targets, $session_id) = @_;
1961 $session_id = 0 if not defined $session_id;
1963 # Switch on or off?
1964 my $state= $st eq 'active' ? 'active': 'locked';
1966 my $ldap_handle = &get_ldap_handle($session_id);
1967 if( defined($ldap_handle) ) {
1969 # Build search filter for hosts
1970 my $search= "(&(objectClass=GOhard)";
1971 foreach (@{$targets}){
1972 $search.= "(macAddress=$_)";
1973 }
1974 $search.= ")";
1976 # If there's any host inside of the search string, procress them
1977 if (!($search =~ /macAddress/)){
1978 return;
1979 }
1981 # Perform search for Unit Tag
1982 my $mesg = $ldap_handle->search(
1983 base => $ldap_base,
1984 scope => 'sub',
1985 attrs => ['dn', 'gotoMode'],
1986 filter => "$search"
1987 );
1989 if ($mesg->count) {
1990 my @entries = $mesg->entries;
1991 foreach my $entry (@entries) {
1993 # Only modify entry if it is not set to '$state'
1994 if ($entry->get_value("gotoMode") ne $state){
1996 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1997 my $result;
1998 $result= $ldap_handle->modify($entry->dn, changes => [
1999 replace => [ gotoMode => $state ] ]);
2001 # Errors?
2002 if ($result->code){
2003 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2004 }
2006 }
2007 }
2008 } else {
2009 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2010 }
2012 }
2013 }
2016 sub run_recreate_packages_db {
2017 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2018 my $session_id = $session->ID;
2019 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 4);
2020 $kernel->yield('create_fai_release_db');
2021 $kernel->yield('create_fai_server_db');
2022 return;
2023 }
2026 sub run_create_fai_server_db {
2027 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2028 my $session_id = $session->ID;
2029 my $task = POE::Wheel::Run->new(
2030 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2031 StdoutEvent => "session_run_result",
2032 StderrEvent => "session_run_debug",
2033 CloseEvent => "session_run_done",
2034 );
2036 $heap->{task}->{ $task->ID } = $task;
2037 return;
2038 }
2041 sub create_fai_server_db {
2042 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2043 my $result;
2045 if (not defined $session_id) { $session_id = 0; }
2046 my $ldap_handle = &get_ldap_handle();
2047 if(defined($ldap_handle)) {
2048 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2049 my $mesg= $ldap_handle->search(
2050 base => $ldap_base,
2051 scope => 'sub',
2052 attrs => ['FAIrepository', 'gosaUnitTag'],
2053 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2054 );
2055 if($mesg->{'resultCode'} == 0 &&
2056 $mesg->count != 0) {
2057 foreach my $entry (@{$mesg->{entries}}) {
2058 if($entry->exists('FAIrepository')) {
2059 # Add an entry for each Repository configured for server
2060 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2061 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2062 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2063 $result= $fai_server_db->add_dbentry( {
2064 table => $table_name,
2065 primkey => ['server', 'release', 'tag'],
2066 server => $tmp_url,
2067 release => $tmp_release,
2068 sections => $tmp_sections,
2069 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2070 } );
2071 }
2072 }
2073 }
2074 }
2075 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2077 # TODO: Find a way to post the 'create_packages_list_db' event
2078 if(not defined($dont_create_packages_list)) {
2079 &create_packages_list_db(undef, undef, $session_id);
2080 }
2081 }
2083 $ldap_handle->disconnect;
2084 return $result;
2085 }
2088 sub run_create_fai_release_db {
2089 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2090 my $session_id = $session->ID;
2091 my $task = POE::Wheel::Run->new(
2092 Program => sub { &create_fai_release_db($table_name, $session_id) },
2093 StdoutEvent => "session_run_result",
2094 StderrEvent => "session_run_debug",
2095 CloseEvent => "session_run_done",
2096 );
2098 $heap->{task}->{ $task->ID } = $task;
2099 return;
2100 }
2103 sub create_fai_release_db {
2104 my ($table_name, $session_id) = @_;
2105 my $result;
2107 # used for logging
2108 if (not defined $session_id) { $session_id = 0; }
2110 my $ldap_handle = &get_ldap_handle();
2111 if(defined($ldap_handle)) {
2112 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2113 my $mesg= $ldap_handle->search(
2114 base => $ldap_base,
2115 scope => 'sub',
2116 attrs => [],
2117 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2118 );
2119 if($mesg->{'resultCode'} == 0 &&
2120 $mesg->count != 0) {
2121 # Walk through all possible FAI container ou's
2122 my @sql_list;
2123 my $timestamp= &get_time();
2124 foreach my $ou (@{$mesg->{entries}}) {
2125 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2126 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2127 my @tmp_array=get_fai_release_entries($tmp_classes);
2128 if(@tmp_array) {
2129 foreach my $entry (@tmp_array) {
2130 if(defined($entry) && ref($entry) eq 'HASH') {
2131 my $sql=
2132 "INSERT INTO $table_name "
2133 ."(timestamp, release, class, type, state) VALUES ("
2134 .$timestamp.","
2135 ."'".$entry->{'release'}."',"
2136 ."'".$entry->{'class'}."',"
2137 ."'".$entry->{'type'}."',"
2138 ."'".$entry->{'state'}."')";
2139 push @sql_list, $sql;
2140 }
2141 }
2142 }
2143 }
2144 }
2146 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2147 if(@sql_list) {
2148 unshift @sql_list, "VACUUM";
2149 unshift @sql_list, "DELETE FROM $table_name";
2150 $fai_release_db->exec_statementlist(\@sql_list);
2151 }
2152 daemon_log("$session_id DEBUG: Done with inserting",7);
2153 }
2154 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2155 }
2156 $ldap_handle->disconnect;
2157 return $result;
2158 }
2160 sub get_fai_types {
2161 my $tmp_classes = shift || return undef;
2162 my @result;
2164 foreach my $type(keys %{$tmp_classes}) {
2165 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2166 my $entry = {
2167 type => $type,
2168 state => $tmp_classes->{$type}[0],
2169 };
2170 push @result, $entry;
2171 }
2172 }
2174 return @result;
2175 }
2177 sub get_fai_state {
2178 my $result = "";
2179 my $tmp_classes = shift || return $result;
2181 foreach my $type(keys %{$tmp_classes}) {
2182 if(defined($tmp_classes->{$type}[0])) {
2183 $result = $tmp_classes->{$type}[0];
2185 # State is equal for all types in class
2186 last;
2187 }
2188 }
2190 return $result;
2191 }
2193 sub resolve_fai_classes {
2194 my ($fai_base, $ldap_handle, $session_id) = @_;
2195 if (not defined $session_id) { $session_id = 0; }
2196 my $result;
2197 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2198 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2199 my $fai_classes;
2201 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2202 my $mesg= $ldap_handle->search(
2203 base => $fai_base,
2204 scope => 'sub',
2205 attrs => ['cn','objectClass','FAIstate'],
2206 filter => $fai_filter,
2207 );
2208 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2210 if($mesg->{'resultCode'} == 0 &&
2211 $mesg->count != 0) {
2212 foreach my $entry (@{$mesg->{entries}}) {
2213 if($entry->exists('cn')) {
2214 my $tmp_dn= $entry->dn();
2216 # Skip classname and ou dn parts for class
2217 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2219 # Skip classes without releases
2220 if((!defined($tmp_release)) || length($tmp_release)==0) {
2221 next;
2222 }
2224 my $tmp_cn= $entry->get_value('cn');
2225 my $tmp_state= $entry->get_value('FAIstate');
2227 my $tmp_type;
2228 # Get FAI type
2229 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2230 if(grep $_ eq $oclass, @possible_fai_classes) {
2231 $tmp_type= $oclass;
2232 last;
2233 }
2234 }
2236 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2237 # A Subrelease
2238 my @sub_releases = split(/,/, $tmp_release);
2240 # Walk through subreleases and build hash tree
2241 my $hash;
2242 while(my $tmp_sub_release = pop @sub_releases) {
2243 $hash .= "\{'$tmp_sub_release'\}->";
2244 }
2245 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2246 } else {
2247 # A branch, no subrelease
2248 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2249 }
2250 } elsif (!$entry->exists('cn')) {
2251 my $tmp_dn= $entry->dn();
2252 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2254 # Skip classes without releases
2255 if((!defined($tmp_release)) || length($tmp_release)==0) {
2256 next;
2257 }
2259 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2260 # A Subrelease
2261 my @sub_releases= split(/,/, $tmp_release);
2263 # Walk through subreleases and build hash tree
2264 my $hash;
2265 while(my $tmp_sub_release = pop @sub_releases) {
2266 $hash .= "\{'$tmp_sub_release'\}->";
2267 }
2268 # Remove the last two characters
2269 chop($hash);
2270 chop($hash);
2272 eval('$fai_classes->'.$hash.'= {}');
2273 } else {
2274 # A branch, no subrelease
2275 if(!exists($fai_classes->{$tmp_release})) {
2276 $fai_classes->{$tmp_release} = {};
2277 }
2278 }
2279 }
2280 }
2282 # The hash is complete, now we can honor the copy-on-write based missing entries
2283 foreach my $release (keys %$fai_classes) {
2284 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2285 }
2286 }
2287 return $result;
2288 }
2290 sub apply_fai_inheritance {
2291 my $fai_classes = shift || return {};
2292 my $tmp_classes;
2294 # Get the classes from the branch
2295 foreach my $class (keys %{$fai_classes}) {
2296 # Skip subreleases
2297 if($class =~ /^ou=.*$/) {
2298 next;
2299 } else {
2300 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2301 }
2302 }
2304 # Apply to each subrelease
2305 foreach my $subrelease (keys %{$fai_classes}) {
2306 if($subrelease =~ /ou=/) {
2307 foreach my $tmp_class (keys %{$tmp_classes}) {
2308 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2309 $fai_classes->{$subrelease}->{$tmp_class} =
2310 deep_copy($tmp_classes->{$tmp_class});
2311 } else {
2312 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2313 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2314 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2315 deep_copy($tmp_classes->{$tmp_class}->{$type});
2316 }
2317 }
2318 }
2319 }
2320 }
2321 }
2323 # Find subreleases in deeper levels
2324 foreach my $subrelease (keys %{$fai_classes}) {
2325 if($subrelease =~ /ou=/) {
2326 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2327 if($subsubrelease =~ /ou=/) {
2328 apply_fai_inheritance($fai_classes->{$subrelease});
2329 }
2330 }
2331 }
2332 }
2334 return $fai_classes;
2335 }
2337 sub get_fai_release_entries {
2338 my $tmp_classes = shift || return;
2339 my $parent = shift || "";
2340 my @result = shift || ();
2342 foreach my $entry (keys %{$tmp_classes}) {
2343 if(defined($entry)) {
2344 if($entry =~ /^ou=.*$/) {
2345 my $release_name = $entry;
2346 $release_name =~ s/ou=//g;
2347 if(length($parent)>0) {
2348 $release_name = $parent."/".$release_name;
2349 }
2350 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2351 foreach my $bufentry(@bufentries) {
2352 push @result, $bufentry;
2353 }
2354 } else {
2355 my @types = get_fai_types($tmp_classes->{$entry});
2356 foreach my $type (@types) {
2357 push @result,
2358 {
2359 'class' => $entry,
2360 'type' => $type->{'type'},
2361 'release' => $parent,
2362 'state' => $type->{'state'},
2363 };
2364 }
2365 }
2366 }
2367 }
2369 return @result;
2370 }
2372 sub deep_copy {
2373 my $this = shift;
2374 if (not ref $this) {
2375 $this;
2376 } elsif (ref $this eq "ARRAY") {
2377 [map deep_copy($_), @$this];
2378 } elsif (ref $this eq "HASH") {
2379 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2380 } else { die "what type is $_?" }
2381 }
2384 sub session_run_result {
2385 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2386 $kernel->sig(CHLD => "child_reap");
2387 }
2389 sub session_run_debug {
2390 my $result = $_[ARG0];
2391 print STDERR "$result\n";
2392 }
2394 sub session_run_done {
2395 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2396 delete $heap->{task}->{$task_id};
2397 }
2400 sub create_sources_list {
2401 my $session_id = shift;
2402 my $ldap_handle = &main::get_ldap_handle;
2403 my $result="/tmp/gosa_si_tmp_sources_list";
2405 # Remove old file
2406 if(stat($result)) {
2407 unlink($result);
2408 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2409 }
2411 my $fh;
2412 open($fh, ">$result");
2413 if (not defined $fh) {
2414 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2415 return undef;
2416 }
2417 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2418 my $mesg=$ldap_handle->search(
2419 base => $main::ldap_server_dn,
2420 scope => 'base',
2421 attrs => 'FAIrepository',
2422 filter => 'objectClass=FAIrepositoryServer'
2423 );
2424 if($mesg->count) {
2425 foreach my $entry(@{$mesg->{'entries'}}) {
2426 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2427 my ($server, $tag, $release, $sections)= split /\|/, $value;
2428 my $line = "deb $server $release";
2429 $sections =~ s/,/ /g;
2430 $line.= " $sections";
2431 print $fh $line."\n";
2432 }
2433 }
2434 }
2435 } else {
2436 if (defined $main::ldap_server_dn){
2437 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2438 } else {
2439 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2440 }
2441 }
2442 close($fh);
2444 return $result;
2445 }
2448 sub run_create_packages_list_db {
2449 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2450 my $session_id = $session->ID;
2452 my $task = POE::Wheel::Run->new(
2453 Priority => +20,
2454 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2455 StdoutEvent => "session_run_result",
2456 StderrEvent => "session_run_debug",
2457 CloseEvent => "session_run_done",
2458 );
2459 $heap->{task}->{ $task->ID } = $task;
2460 }
2463 sub create_packages_list_db {
2464 my ($ldap_handle, $sources_file, $session_id) = @_;
2466 # it should not be possible to trigger a recreation of packages_list_db
2467 # while packages_list_db is under construction, so set flag packages_list_under_construction
2468 # which is tested befor recreation can be started
2469 if (-r $packages_list_under_construction) {
2470 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2471 return;
2472 } else {
2473 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2474 # set packages_list_under_construction to true
2475 system("touch $packages_list_under_construction");
2476 @packages_list_statements=();
2477 }
2479 if (not defined $session_id) { $session_id = 0; }
2480 if (not defined $ldap_handle) {
2481 $ldap_handle= &get_ldap_handle();
2483 if (not defined $ldap_handle) {
2484 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2485 unlink($packages_list_under_construction);
2486 return;
2487 }
2488 }
2489 if (not defined $sources_file) {
2490 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2491 $sources_file = &create_sources_list($session_id);
2492 }
2494 if (not defined $sources_file) {
2495 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2496 unlink($packages_list_under_construction);
2497 return;
2498 }
2500 my $line;
2502 open(CONFIG, "<$sources_file") or do {
2503 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2504 unlink($packages_list_under_construction);
2505 return;
2506 };
2508 # Read lines
2509 while ($line = <CONFIG>){
2510 # Unify
2511 chop($line);
2512 $line =~ s/^\s+//;
2513 $line =~ s/^\s+/ /;
2515 # Strip comments
2516 $line =~ s/#.*$//g;
2518 # Skip empty lines
2519 if ($line =~ /^\s*$/){
2520 next;
2521 }
2523 # Interpret deb line
2524 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2525 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2526 my $section;
2527 foreach $section (split(' ', $sections)){
2528 &parse_package_info( $baseurl, $dist, $section, $session_id );
2529 }
2530 }
2531 }
2533 close (CONFIG);
2535 find(\&cleanup_and_extract, keys( %repo_dirs ));
2536 &main::strip_packages_list_statements();
2537 unshift @packages_list_statements, "VACUUM";
2538 $packages_list_db->exec_statementlist(\@packages_list_statements);
2539 unlink($packages_list_under_construction);
2540 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2541 return;
2542 }
2544 # This function should do some intensive task to minimize the db-traffic
2545 sub strip_packages_list_statements {
2546 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2547 my @new_statement_list=();
2548 my $hash;
2549 my $insert_hash;
2550 my $update_hash;
2551 my $delete_hash;
2552 my $local_timestamp=get_time();
2554 foreach my $existing_entry (@existing_entries) {
2555 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2556 }
2558 foreach my $statement (@packages_list_statements) {
2559 if($statement =~ /^INSERT/i) {
2560 # Assign the values from the insert statement
2561 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2562 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2563 if(exists($hash->{$distribution}->{$package}->{$version})) {
2564 # If section or description has changed, update the DB
2565 if(
2566 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2567 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2568 ) {
2569 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2570 }
2571 } else {
2572 # Insert a non-existing entry to db
2573 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2574 }
2575 } elsif ($statement =~ /^UPDATE/i) {
2576 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2577 /^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;
2578 foreach my $distribution (keys %{$hash}) {
2579 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2580 # update the insertion hash to execute only one query per package (insert instead insert+update)
2581 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2582 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2583 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2584 my $section;
2585 my $description;
2586 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2587 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2588 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2589 }
2590 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2591 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2592 }
2593 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2594 }
2595 }
2596 }
2597 }
2598 }
2600 # TODO: Check for orphaned entries
2602 # unroll the insert_hash
2603 foreach my $distribution (keys %{$insert_hash}) {
2604 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2605 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2606 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2607 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2608 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2609 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2610 ."'$local_timestamp')";
2611 }
2612 }
2613 }
2615 # unroll the update hash
2616 foreach my $distribution (keys %{$update_hash}) {
2617 foreach my $package (keys %{$update_hash->{$distribution}}) {
2618 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2619 my $set = "";
2620 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2621 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2622 }
2623 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2624 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2625 }
2626 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2627 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2628 }
2629 if(defined($set) and length($set) > 0) {
2630 $set .= "timestamp = '$local_timestamp'";
2631 } else {
2632 next;
2633 }
2634 push @new_statement_list,
2635 "UPDATE $main::packages_list_tn SET $set WHERE"
2636 ." distribution = '$distribution'"
2637 ." AND package = '$package'"
2638 ." AND version = '$version'";
2639 }
2640 }
2641 }
2643 @packages_list_statements = @new_statement_list;
2644 }
2647 sub parse_package_info {
2648 my ($baseurl, $dist, $section, $session_id)= @_;
2649 my ($package);
2650 if (not defined $session_id) { $session_id = 0; }
2651 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2652 $repo_dirs{ "${repo_path}/pool" } = 1;
2654 foreach $package ("Packages.gz"){
2655 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2656 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2657 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2658 }
2660 }
2663 sub get_package {
2664 my ($url, $dest, $session_id)= @_;
2665 if (not defined $session_id) { $session_id = 0; }
2667 my $tpath = dirname($dest);
2668 -d "$tpath" || mkpath "$tpath";
2670 # This is ugly, but I've no time to take a look at "how it works in perl"
2671 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2672 system("gunzip -cd '$dest' > '$dest.in'");
2673 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2674 unlink($dest);
2675 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2676 } else {
2677 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2678 }
2679 return 0;
2680 }
2683 sub parse_package {
2684 my ($path, $dist, $srv_path, $session_id)= @_;
2685 if (not defined $session_id) { $session_id = 0;}
2686 my ($package, $version, $section, $description);
2687 my $PACKAGES;
2688 my $timestamp = &get_time();
2690 if(not stat("$path.in")) {
2691 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2692 return;
2693 }
2695 open($PACKAGES, "<$path.in");
2696 if(not defined($PACKAGES)) {
2697 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2698 return;
2699 }
2701 # Read lines
2702 while (<$PACKAGES>){
2703 my $line = $_;
2704 # Unify
2705 chop($line);
2707 # Use empty lines as a trigger
2708 if ($line =~ /^\s*$/){
2709 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2710 push(@packages_list_statements, $sql);
2711 $package = "none";
2712 $version = "none";
2713 $section = "none";
2714 $description = "none";
2715 next;
2716 }
2718 # Trigger for package name
2719 if ($line =~ /^Package:\s/){
2720 ($package)= ($line =~ /^Package: (.*)$/);
2721 next;
2722 }
2724 # Trigger for version
2725 if ($line =~ /^Version:\s/){
2726 ($version)= ($line =~ /^Version: (.*)$/);
2727 next;
2728 }
2730 # Trigger for description
2731 if ($line =~ /^Description:\s/){
2732 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2733 next;
2734 }
2736 # Trigger for section
2737 if ($line =~ /^Section:\s/){
2738 ($section)= ($line =~ /^Section: (.*)$/);
2739 next;
2740 }
2742 # Trigger for filename
2743 if ($line =~ /^Filename:\s/){
2744 my ($filename) = ($line =~ /^Filename: (.*)$/);
2745 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2746 next;
2747 }
2748 }
2750 close( $PACKAGES );
2751 unlink( "$path.in" );
2752 }
2755 sub store_fileinfo {
2756 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2758 my %fileinfo = (
2759 'package' => $package,
2760 'dist' => $dist,
2761 'version' => $vers,
2762 );
2764 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2765 }
2768 sub cleanup_and_extract {
2769 my $fileinfo = $repo_files{ $File::Find::name };
2771 if( defined $fileinfo ) {
2773 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2774 my $sql;
2775 my $package = $fileinfo->{ 'package' };
2776 my $newver = $fileinfo->{ 'version' };
2778 mkpath($dir);
2779 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2781 if( -f "$dir/DEBIAN/templates" ) {
2783 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2785 my $tmpl= "";
2786 {
2787 local $/=undef;
2788 open FILE, "$dir/DEBIAN/templates";
2789 $tmpl = &encode_base64(<FILE>);
2790 close FILE;
2791 }
2792 rmtree("$dir/DEBIAN/templates");
2794 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2795 push @packages_list_statements, $sql;
2796 }
2797 }
2799 return;
2800 }
2803 sub register_at_foreign_servers {
2804 my ($kernel) = $_[KERNEL];
2806 # hole alle bekannten server aus known_server_db
2807 my $server_sql = "SELECT * FROM $known_server_tn";
2808 my $server_res = $known_server_db->exec_statement($server_sql);
2810 # no entries in known_server_db
2811 if (not ref(@$server_res[0]) eq "ARRAY") {
2812 # TODO
2813 }
2815 # detect already connected clients
2816 my $client_sql = "SELECT * FROM $known_clients_tn";
2817 my $client_res = $known_clients_db->exec_statement($client_sql);
2819 # send my server details to all other gosa-si-server within the network
2820 foreach my $hit (@$server_res) {
2821 my $hostname = @$hit[0];
2822 my $hostkey = &create_passwd;
2824 # add already connected clients to registration message
2825 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2826 &add_content2xml_hash($myhash, 'key', $hostkey);
2827 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2829 # build registration message and send it
2830 my $foreign_server_msg = &create_xml_string($myhash);
2831 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2832 }
2834 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2835 return;
2836 }
2839 #==== MAIN = main ==============================================================
2840 # parse commandline options
2841 Getopt::Long::Configure( "bundling" );
2842 GetOptions("h|help" => \&usage,
2843 "c|config=s" => \$cfg_file,
2844 "f|foreground" => \$foreground,
2845 "v|verbose+" => \$verbose,
2846 "no-arp+" => \$no_arp,
2847 );
2849 # read and set config parameters
2850 &check_cmdline_param ;
2851 &read_configfile;
2852 &check_pid;
2854 $SIG{CHLD} = 'IGNORE';
2856 # forward error messages to logfile
2857 if( ! $foreground ) {
2858 open( STDIN, '+>/dev/null' );
2859 open( STDOUT, '+>&STDIN' );
2860 open( STDERR, '+>&STDIN' );
2861 }
2863 # Just fork, if we are not in foreground mode
2864 if( ! $foreground ) {
2865 chdir '/' or die "Can't chdir to /: $!";
2866 $pid = fork;
2867 setsid or die "Can't start a new session: $!";
2868 umask 0;
2869 } else {
2870 $pid = $$;
2871 }
2873 # Do something useful - put our PID into the pid_file
2874 if( 0 != $pid ) {
2875 open( LOCK_FILE, ">$pid_file" );
2876 print LOCK_FILE "$pid\n";
2877 close( LOCK_FILE );
2878 if( !$foreground ) {
2879 exit( 0 )
2880 };
2881 }
2883 # parse head url and revision from svn
2884 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2885 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2886 $server_headURL = defined $1 ? $1 : 'unknown' ;
2887 $server_revision = defined $2 ? $2 : 'unknown' ;
2888 if ($server_headURL =~ /\/tag\// ||
2889 $server_headURL =~ /\/branches\// ) {
2890 $server_status = "stable";
2891 } else {
2892 $server_status = "developmental" ;
2893 }
2896 daemon_log(" ", 1);
2897 daemon_log("$0 started!", 1);
2898 daemon_log("status: $server_status", 1);
2899 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2901 # connect to incoming_db
2902 unlink($incoming_file_name);
2903 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2904 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2906 # connect to gosa-si job queue
2907 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2908 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2910 # connect to known_clients_db
2911 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2912 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2914 # connect to foreign_clients_db
2915 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2916 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2918 # connect to known_server_db
2919 unlink($known_server_file_name);
2920 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2921 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2923 # connect to login_usr_db
2924 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2925 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2927 # connect to fai_server_db and fai_release_db
2928 unlink($fai_server_file_name);
2929 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2930 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2932 unlink($fai_release_file_name);
2933 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2934 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2936 # connect to packages_list_db
2937 #unlink($packages_list_file_name);
2938 unlink($packages_list_under_construction);
2939 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2940 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2942 # connect to messaging_db
2943 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2944 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2947 # create xml object used for en/decrypting
2948 $xml = new XML::Simple();
2951 # foreign servers
2952 my @foreign_server_list;
2954 # add foreign server from cfg file
2955 if ($foreign_server_string ne "") {
2956 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2957 foreach my $foreign_server (@cfg_foreign_server_list) {
2958 push(@foreign_server_list, $foreign_server);
2959 }
2960 }
2962 # add foreign server from dns
2963 my @tmp_servers;
2964 if ( !$server_domain) {
2965 # Try our DNS Searchlist
2966 for my $domain(get_dns_domains()) {
2967 chomp($domain);
2968 my @tmp_domains= &get_server_addresses($domain);
2969 if(@tmp_domains) {
2970 for my $tmp_server(@tmp_domains) {
2971 push @tmp_servers, $tmp_server;
2972 }
2973 }
2974 }
2975 if(@tmp_servers && length(@tmp_servers)==0) {
2976 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2977 }
2978 } else {
2979 @tmp_servers = &get_server_addresses($server_domain);
2980 if( 0 == @tmp_servers ) {
2981 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2982 }
2983 }
2984 foreach my $server (@tmp_servers) {
2985 unshift(@foreign_server_list, $server);
2986 }
2987 # eliminate duplicate entries
2988 @foreign_server_list = &del_doubles(@foreign_server_list);
2989 my $all_foreign_server = join(", ", @foreign_server_list);
2990 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2992 # add all found foreign servers to known_server
2993 my $act_timestamp = &get_time();
2994 foreach my $foreign_server (@foreign_server_list) {
2996 # do not add myself to known_server_db
2997 if (&is_local($foreign_server)) { next; }
2998 ######################################
3000 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3001 primkey=>['hostname'],
3002 hostname=>$foreign_server,
3003 status=>'not_jet_registered',
3004 hostkey=>"none",
3005 timestamp=>$act_timestamp,
3006 } );
3007 }
3010 POE::Component::Server::TCP->new(
3011 Alias => "TCP_SERVER",
3012 Port => $server_port,
3013 ClientInput => sub {
3014 my ($kernel, $input) = @_[KERNEL, ARG0];
3015 push(@tasks, $input);
3016 push(@msgs_to_decrypt, $input);
3017 $kernel->yield("msg_to_decrypt");
3018 },
3019 InlineStates => {
3020 msg_to_decrypt => \&msg_to_decrypt,
3021 next_task => \&next_task,
3022 task_result => \&handle_task_result,
3023 task_done => \&handle_task_done,
3024 task_debug => \&handle_task_debug,
3025 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3026 }
3027 );
3029 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3031 # create session for repeatedly checking the job queue for jobs
3032 POE::Session->create(
3033 inline_states => {
3034 _start => \&session_start,
3035 register_at_foreign_servers => \®ister_at_foreign_servers,
3036 sig_handler => \&sig_handler,
3037 next_task => \&next_task,
3038 task_result => \&handle_task_result,
3039 task_done => \&handle_task_done,
3040 task_debug => \&handle_task_debug,
3041 watch_for_next_tasks => \&watch_for_next_tasks,
3042 watch_for_new_messages => \&watch_for_new_messages,
3043 watch_for_delivery_messages => \&watch_for_delivery_messages,
3044 watch_for_done_messages => \&watch_for_done_messages,
3045 watch_for_new_jobs => \&watch_for_new_jobs,
3046 watch_for_modified_jobs => \&watch_for_modified_jobs,
3047 watch_for_done_jobs => \&watch_for_done_jobs,
3048 watch_for_old_known_clients => \&watch_for_old_known_clients,
3049 create_packages_list_db => \&run_create_packages_list_db,
3050 create_fai_server_db => \&run_create_fai_server_db,
3051 create_fai_release_db => \&run_create_fai_release_db,
3052 recreate_packages_db => \&run_recreate_packages_db,
3053 session_run_result => \&session_run_result,
3054 session_run_debug => \&session_run_debug,
3055 session_run_done => \&session_run_done,
3056 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3057 }
3058 );
3061 # import all modules
3062 &import_modules;
3064 # TODO
3065 # check wether all modules are gosa-si valid passwd check
3069 POE::Kernel->run();
3070 exit;