1a79a57830bfb44b6138d1da1a439bcf9d7800b8
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_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 $arp_enabled, $arp_interface,
91 );
93 # additional variable which should be globaly accessable
94 our $server_address;
95 our $server_mac_address;
96 our $gosa_address;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 # specifies the verbosity of the daemon_log
104 $verbose = 0 ;
106 # if foreground is not null, script will be not forked to background
107 $foreground = 0 ;
109 # specifies the timeout seconds while checking the online status of a registrating client
110 $ping_timeout = 5;
112 $no_arp = 0;
113 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
114 my @packages_list_statements;
115 my $watch_for_new_jobs_in_progress = 0;
117 # holds all incoming decrypted messages
118 our $incoming_db;
119 our $incoming_tn = 'incoming';
120 my $incoming_file_name;
121 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
122 "timestamp DEFAULT 'none'",
123 "headertag DEFAULT 'none'",
124 "targettag DEFAULT 'none'",
125 "xmlmessage DEFAULT 'none'",
126 "module DEFAULT 'none'",
127 "sessionid DEFAULT '0'",
128 );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
135 "timestamp DEFAULT 'none'",
136 "status DEFAULT 'none'",
137 "result DEFAULT 'none'",
138 "progress DEFAULT 'none'",
139 "headertag DEFAULT 'none'",
140 "targettag DEFAULT 'none'",
141 "xmlmessage DEFAULT 'none'",
142 "macaddress DEFAULT 'none'",
143 "plainname DEFAULT 'none'",
144 "siserver DEFAULT 'none'",
145 "modified DEFAULT '0'",
146 );
148 # holds all other gosa-si-server
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients";
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server";
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release";
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386";
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging";
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
195 "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai";
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 %cfg_defaults = (
208 "general" => {
209 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
210 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
211 },
212 "server" => {
213 "ip" => [\$server_ip, "0.0.0.0"],
214 "port" => [\$server_port, "20081"],
215 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
216 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
217 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
218 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
219 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
220 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
221 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
222 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
223 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
224 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
225 "repo-path" => [\$repo_path, '/srv/www/repository'],
226 "ldap-uri" => [\$ldap_uri, ""],
227 "ldap-base" => [\$ldap_base, ""],
228 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
229 "ldap-admin-password" => [\$ldap_admin_password, ""],
230 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
231 "max-clients" => [\$max_clients, 10],
232 "wol-password" => [\$wake_on_lan_passwd, ""],
233 },
234 "GOsaPackages" => {
235 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
236 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
237 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
238 "key" => [\$GosaPackages_key, "none"],
239 },
240 "ClientPackages" => {
241 "key" => [\$ClientPackages_key, "none"],
242 },
243 "ServerPackages"=> {
244 "address" => [\$foreign_server_string, ""],
245 "domain" => [\$server_domain, ""],
246 "key" => [\$ServerPackages_key, "none"],
247 "key-lifetime" => [\$foreign_servers_register_delay, 120],
248 "job-synchronization-enabled" => [\$job_synchronization, "true"],
249 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
250 },
251 "ArpHandler" => {
252 "enabled" => [\$arp_enabled, "true"],
253 "interface" => [\$arp_interface, "all"],
254 },
256 );
259 #=== FUNCTION ================================================================
260 # NAME: usage
261 # PARAMETERS: nothing
262 # RETURNS: nothing
263 # DESCRIPTION: print out usage text to STDERR
264 #===============================================================================
265 sub usage {
266 print STDERR << "EOF" ;
267 usage: $prg [-hvf] [-c config]
269 -h : this (help) message
270 -c <file> : config file
271 -f : foreground, process will not be forked to background
272 -v : be verbose (multiple to increase verbosity)
273 -no-arp : starts $prg without connection to arp module
275 EOF
276 print "\n" ;
277 }
280 #=== FUNCTION ================================================================
281 # NAME: logging
282 # PARAMETERS: level - string - default 'info'
283 # msg - string -
284 # facility - string - default 'LOG_DAEMON'
285 # RETURNS: nothing
286 # DESCRIPTION: function for logging
287 #===============================================================================
288 sub daemon_log {
289 # log into log_file
290 my( $msg, $level ) = @_;
291 if(not defined $msg) { return }
292 if(not defined $level) { $level = 1 }
293 if(defined $log_file){
294 open(LOG_HANDLE, ">>$log_file");
295 chmod 0600, $log_file;
296 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
297 print STDERR "cannot open $log_file: $!";
298 return
299 }
300 chomp($msg);
301 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
302 if($level <= $verbose){
303 my ($seconds, $minutes, $hours, $monthday, $month,
304 $year, $weekday, $yearday, $sommertime) = localtime(time);
305 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
306 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
307 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
308 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
309 $month = $monthnames[$month];
310 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
311 $year+=1900;
312 my $name = $prg;
314 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
315 print LOG_HANDLE $log_msg;
316 if( $foreground ) {
317 print STDERR $log_msg;
318 }
319 }
320 close( LOG_HANDLE );
321 }
322 }
325 #=== FUNCTION ================================================================
326 # NAME: check_cmdline_param
327 # PARAMETERS: nothing
328 # RETURNS: nothing
329 # DESCRIPTION: validates commandline parameter
330 #===============================================================================
331 sub check_cmdline_param () {
332 my $err_config;
333 my $err_counter = 0;
334 if(not defined($cfg_file)) {
335 $cfg_file = "/etc/gosa-si/server.conf";
336 if(! -r $cfg_file) {
337 $err_config = "please specify a config file";
338 $err_counter += 1;
339 }
340 }
341 if( $err_counter > 0 ) {
342 &usage( "", 1 );
343 if( defined( $err_config)) { print STDERR "$err_config\n"}
344 print STDERR "\n";
345 exit( -1 );
346 }
347 }
350 #=== FUNCTION ================================================================
351 # NAME: check_pid
352 # PARAMETERS: nothing
353 # RETURNS: nothing
354 # DESCRIPTION: handels pid processing
355 #===============================================================================
356 sub check_pid {
357 $pid = -1;
358 # Check, if we are already running
359 if( open(LOCK_FILE, "<$pid_file") ) {
360 $pid = <LOCK_FILE>;
361 if( defined $pid ) {
362 chomp( $pid );
363 if( -f "/proc/$pid/stat" ) {
364 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
365 if( $stat ) {
366 daemon_log("ERROR: Already running",1);
367 close( LOCK_FILE );
368 exit -1;
369 }
370 }
371 }
372 close( LOCK_FILE );
373 unlink( $pid_file );
374 }
376 # create a syslog msg if it is not to possible to open PID file
377 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
378 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
379 if (open(LOCK_FILE, '<', $pid_file)
380 && ($pid = <LOCK_FILE>))
381 {
382 chomp($pid);
383 $msg .= "(PID $pid)\n";
384 } else {
385 $msg .= "(unable to read PID)\n";
386 }
387 if( ! ($foreground) ) {
388 openlog( $0, "cons,pid", "daemon" );
389 syslog( "warning", $msg );
390 closelog();
391 }
392 else {
393 print( STDERR " $msg " );
394 }
395 exit( -1 );
396 }
397 }
399 #=== FUNCTION ================================================================
400 # NAME: import_modules
401 # PARAMETERS: module_path - string - abs. path to the directory the modules
402 # are stored
403 # RETURNS: nothing
404 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
405 # state is on is imported by "require 'file';"
406 #===============================================================================
407 sub import_modules {
408 daemon_log(" ", 1);
410 if (not -e $modules_path) {
411 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
412 }
414 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
415 while (defined (my $file = readdir (DIR))) {
416 if (not $file =~ /(\S*?).pm$/) {
417 next;
418 }
419 my $mod_name = $1;
421 # ArpHandler switch
422 if( $file =~ /ArpHandler.pm/ ) {
423 if( $arp_enabled eq "false" ) { next; }
424 }
426 eval { require $file; };
427 if ($@) {
428 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
429 daemon_log("$@", 5);
430 } else {
431 my $info = eval($mod_name.'::get_module_info()');
432 # Only load module if get_module_info() returns a non-null object
433 if( $info ) {
434 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
435 $known_modules->{$mod_name} = $info;
436 daemon_log("0 INFO: module $mod_name loaded", 5);
437 }
438 }
439 }
440 close (DIR);
441 }
443 #=== FUNCTION ================================================================
444 # NAME: password_check
445 # PARAMETERS: nothing
446 # RETURNS: nothing
447 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
448 # the same password
449 #===============================================================================
450 sub password_check {
451 my $passwd_hash = {};
452 while (my ($mod_name, $mod_info) = each %$known_modules) {
453 my $mod_passwd = @$mod_info[1];
454 if (not defined $mod_passwd) { next; }
455 if (not exists $passwd_hash->{$mod_passwd}) {
456 $passwd_hash->{$mod_passwd} = $mod_name;
458 # escalates critical error
459 } else {
460 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
461 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
462 exit( -1 );
463 }
464 }
466 }
469 #=== FUNCTION ================================================================
470 # NAME: sig_int_handler
471 # PARAMETERS: signal - string - signal arose from system
472 # RETURNS: nothing
473 # DESCRIPTION: handels tasks to be done befor signal becomes active
474 #===============================================================================
475 sub sig_int_handler {
476 my ($signal) = @_;
478 # if (defined($ldap_handle)) {
479 # $ldap_handle->disconnect;
480 # }
481 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
484 daemon_log("shutting down gosa-si-server", 1);
485 system("kill `ps -C gosa-si-server -o pid=`");
486 }
487 $SIG{INT} = \&sig_int_handler;
490 sub check_key_and_xml_validity {
491 my ($crypted_msg, $module_key, $session_id) = @_;
492 my $msg;
493 my $msg_hash;
494 my $error_string;
495 eval{
496 $msg = &decrypt_msg($crypted_msg, $module_key);
498 if ($msg =~ /<xml>/i){
499 $msg =~ s/\s+/ /g; # just for better daemon_log
500 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
501 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
503 ##############
504 # check header
505 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
506 my $header_l = $msg_hash->{'header'};
507 if( 1 > @{$header_l} ) { die 'empty header tag'; }
508 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
509 my $header = @{$header_l}[0];
510 if( 0 == length $header) { die 'empty string in header tag'; }
512 ##############
513 # check source
514 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
515 my $source_l = $msg_hash->{'source'};
516 if( 1 > @{$source_l} ) { die 'empty source tag'; }
517 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
518 my $source = @{$source_l}[0];
519 if( 0 == length $source) { die 'source error'; }
521 ##############
522 # check target
523 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
524 my $target_l = $msg_hash->{'target'};
525 if( 1 > @{$target_l} ) { die 'empty target tag'; }
526 }
527 };
528 if($@) {
529 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
530 $msg = undef;
531 $msg_hash = undef;
532 }
534 return ($msg, $msg_hash);
535 }
538 sub check_outgoing_xml_validity {
539 my ($msg, $session_id) = @_;
541 my $msg_hash;
542 eval{
543 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
545 ##############
546 # check header
547 my $header_l = $msg_hash->{'header'};
548 if( 1 != @{$header_l} ) {
549 die 'no or more than one headers specified';
550 }
551 my $header = @{$header_l}[0];
552 if( 0 == length $header) {
553 die 'header has length 0';
554 }
556 ##############
557 # check source
558 my $source_l = $msg_hash->{'source'};
559 if( 1 != @{$source_l} ) {
560 die 'no or more than 1 sources specified';
561 }
562 my $source = @{$source_l}[0];
563 if( 0 == length $source) {
564 die 'source has length 0';
565 }
566 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
567 $source =~ /^GOSA$/i ) {
568 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
569 }
571 ##############
572 # check target
573 my $target_l = $msg_hash->{'target'};
574 if( 0 == @{$target_l} ) {
575 die "no targets specified";
576 }
577 foreach my $target (@$target_l) {
578 if( 0 == length $target) {
579 die "target has length 0";
580 }
581 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582 $target =~ /^GOSA$/i ||
583 $target =~ /^\*$/ ||
584 $target =~ /KNOWN_SERVER/i ||
585 $target =~ /JOBDB/i ||
586 $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 ){
587 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
588 }
589 }
590 };
591 if($@) {
592 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
593 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
594 $msg_hash = undef;
595 }
597 return ($msg_hash);
598 }
601 sub input_from_known_server {
602 my ($input, $remote_ip, $session_id) = @_ ;
603 my ($msg, $msg_hash, $module);
605 my $sql_statement= "SELECT * FROM known_server";
606 my $query_res = $known_server_db->select_dbentry( $sql_statement );
608 while( my ($hit_num, $hit) = each %{ $query_res } ) {
609 my $host_name = $hit->{hostname};
610 if( not $host_name =~ "^$remote_ip") {
611 next;
612 }
613 my $host_key = $hit->{hostkey};
614 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
615 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
617 # check if module can open msg envelope with module key
618 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
619 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
620 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
621 daemon_log("$@", 8);
622 next;
623 }
624 else {
625 $msg = $tmp_msg;
626 $msg_hash = $tmp_msg_hash;
627 $module = "ServerPackages";
628 last;
629 }
630 }
632 if( (!$msg) || (!$msg_hash) || (!$module) ) {
633 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
634 }
636 return ($msg, $msg_hash, $module);
637 }
640 sub input_from_known_client {
641 my ($input, $remote_ip, $session_id) = @_ ;
642 my ($msg, $msg_hash, $module);
644 my $sql_statement= "SELECT * FROM known_clients";
645 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
646 while( my ($hit_num, $hit) = each %{ $query_res } ) {
647 my $host_name = $hit->{hostname};
648 if( not $host_name =~ /^$remote_ip:\d*$/) {
649 next;
650 }
651 my $host_key = $hit->{hostkey};
652 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
653 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
655 # check if module can open msg envelope with module key
656 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
658 if( (!$msg) || (!$msg_hash) ) {
659 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
660 &daemon_log("$@", 8);
661 next;
662 }
663 else {
664 $module = "ClientPackages";
665 last;
666 }
667 }
669 if( (!$msg) || (!$msg_hash) || (!$module) ) {
670 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
671 }
673 return ($msg, $msg_hash, $module);
674 }
677 sub input_from_unknown_host {
678 no strict "refs";
679 my ($input, $session_id) = @_ ;
680 my ($msg, $msg_hash, $module);
681 my $error_string;
683 my %act_modules = %$known_modules;
685 while( my ($mod, $info) = each(%act_modules)) {
687 # check a key exists for this module
688 my $module_key = ${$mod."_key"};
689 if( not defined $module_key ) {
690 if( $mod eq 'ArpHandler' ) {
691 next;
692 }
693 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
694 next;
695 }
696 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
698 # check if module can open msg envelope with module key
699 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
700 if( (not defined $msg) || (not defined $msg_hash) ) {
701 next;
702 }
703 else {
704 $module = $mod;
705 last;
706 }
707 }
709 if( (!$msg) || (!$msg_hash) || (!$module)) {
710 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
711 }
713 return ($msg, $msg_hash, $module);
714 }
717 sub create_ciphering {
718 my ($passwd) = @_;
719 if((!defined($passwd)) || length($passwd)==0) {
720 $passwd = "";
721 }
722 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
723 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
724 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
725 $my_cipher->set_iv($iv);
726 return $my_cipher;
727 }
730 sub encrypt_msg {
731 my ($msg, $key) = @_;
732 my $my_cipher = &create_ciphering($key);
733 my $len;
734 {
735 use bytes;
736 $len= 16-length($msg)%16;
737 }
738 $msg = "\0"x($len).$msg;
739 $msg = $my_cipher->encrypt($msg);
740 chomp($msg = &encode_base64($msg));
741 # there are no newlines allowed inside msg
742 $msg=~ s/\n//g;
743 return $msg;
744 }
747 sub decrypt_msg {
749 my ($msg, $key) = @_ ;
750 $msg = &decode_base64($msg);
751 my $my_cipher = &create_ciphering($key);
752 $msg = $my_cipher->decrypt($msg);
753 $msg =~ s/\0*//g;
754 return $msg;
755 }
758 sub get_encrypt_key {
759 my ($target) = @_ ;
760 my $encrypt_key;
761 my $error = 0;
763 # target can be in known_server
764 if( not defined $encrypt_key ) {
765 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
766 my $query_res = $known_server_db->select_dbentry( $sql_statement );
767 while( my ($hit_num, $hit) = each %{ $query_res } ) {
768 my $host_name = $hit->{hostname};
769 if( $host_name ne $target ) {
770 next;
771 }
772 $encrypt_key = $hit->{hostkey};
773 last;
774 }
775 }
777 # target can be in known_client
778 if( not defined $encrypt_key ) {
779 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
780 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
781 while( my ($hit_num, $hit) = each %{ $query_res } ) {
782 my $host_name = $hit->{hostname};
783 if( $host_name ne $target ) {
784 next;
785 }
786 $encrypt_key = $hit->{hostkey};
787 last;
788 }
789 }
791 return $encrypt_key;
792 }
795 #=== FUNCTION ================================================================
796 # NAME: open_socket
797 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
798 # [PeerPort] string necessary if port not appended by PeerAddr
799 # RETURNS: socket IO::Socket::INET
800 # DESCRIPTION: open a socket to PeerAddr
801 #===============================================================================
802 sub open_socket {
803 my ($PeerAddr, $PeerPort) = @_ ;
804 if(defined($PeerPort)){
805 $PeerAddr = $PeerAddr.":".$PeerPort;
806 }
807 my $socket;
808 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
809 Porto => "tcp",
810 Type => SOCK_STREAM,
811 Timeout => 5,
812 );
813 if(not defined $socket) {
814 return;
815 }
816 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
817 return $socket;
818 }
821 sub get_local_ip_for_remote_ip {
822 my $remote_ip= shift;
823 my $result="0.0.0.0";
825 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
826 if($remote_ip eq "127.0.0.1") {
827 $result = "127.0.0.1";
828 } else {
829 my $PROC_NET_ROUTE= ('/proc/net/route');
831 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
832 or die "Could not open $PROC_NET_ROUTE";
834 my @ifs = <PROC_NET_ROUTE>;
836 close(PROC_NET_ROUTE);
838 # Eat header line
839 shift @ifs;
840 chomp @ifs;
841 foreach my $line(@ifs) {
842 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
843 my $destination;
844 my $mask;
845 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
846 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
847 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
848 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
849 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
850 # destination matches route, save mac and exit
851 $result= &get_ip($Iface);
852 last;
853 }
854 }
855 }
856 } else {
857 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
858 }
859 return $result;
860 }
863 sub send_msg_to_target {
864 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
865 my $error = 0;
866 my $header;
867 my $timestamp = &get_time();
868 my $new_status;
869 my $act_status;
870 my ($sql_statement, $res);
872 if( $msg_header ) {
873 $header = "'$msg_header'-";
874 } else {
875 $header = "";
876 }
878 # Patch the source ip
879 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
880 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
881 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
882 }
884 # encrypt xml msg
885 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
887 # opensocket
888 my $socket = &open_socket($address);
889 if( !$socket ) {
890 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
891 $error++;
892 }
894 if( $error == 0 ) {
895 # send xml msg
896 print $socket $crypted_msg."\n";
898 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
899 daemon_log("$session_id DEBUG: message:\n$msg", 9);
901 }
903 # close socket in any case
904 if( $socket ) {
905 close $socket;
906 }
908 if( $error > 0 ) { $new_status = "down"; }
909 else { $new_status = $msg_header; }
912 # known_clients
913 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
914 $res = $known_clients_db->select_dbentry($sql_statement);
915 if( keys(%$res) == 1) {
916 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
917 if ($act_status eq "down" && $new_status eq "down") {
918 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
919 $res = $known_clients_db->del_dbentry($sql_statement);
920 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
921 } else {
922 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
923 $res = $known_clients_db->update_dbentry($sql_statement);
924 if($new_status eq "down"){
925 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
926 } else {
927 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
928 }
929 }
930 }
932 # known_server
933 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
934 $res = $known_server_db->select_dbentry($sql_statement);
935 if( keys(%$res) == 1) {
936 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
937 if ($act_status eq "down" && $new_status eq "down") {
938 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
939 $res = $known_server_db->del_dbentry($sql_statement);
940 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
941 }
942 else {
943 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
944 $res = $known_server_db->update_dbentry($sql_statement);
945 if($new_status eq "down"){
946 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
947 } else {
948 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
949 }
950 }
951 }
952 return $error;
953 }
956 sub update_jobdb_status_for_send_msgs {
957 my ($answer, $error) = @_;
958 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
959 my $jobdb_id = $1;
961 # sending msg faild
962 if( $error ) {
963 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
964 my $sql_statement = "UPDATE $job_queue_tn ".
965 "SET status='error', result='can not deliver msg, please consult log file' ".
966 "WHERE id=$jobdb_id";
967 my $res = $job_db->update_dbentry($sql_statement);
968 }
970 # sending msg was successful
971 } else {
972 my $sql_statement = "UPDATE $job_queue_tn ".
973 "SET status='done' ".
974 "WHERE id=$jobdb_id AND status='processed'";
975 my $res = $job_db->update_dbentry($sql_statement);
976 }
977 }
978 }
981 sub sig_handler {
982 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
983 daemon_log("0 INFO got signal '$signal'", 1);
984 $kernel->sig_handled();
985 return;
986 }
989 sub msg_to_decrypt {
990 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
991 my $session_id = $session->ID;
992 my ($msg, $msg_hash, $module);
993 my $error = 0;
995 # hole neue msg aus @msgs_to_decrypt
996 my $next_msg = shift @msgs_to_decrypt;
998 # entschlüssle sie
1000 # msg is from a new client or gosa
1001 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1002 # msg is from a gosa-si-server
1003 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1004 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1005 }
1006 # msg is from a gosa-si-client
1007 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1008 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1009 }
1010 # an error occurred
1011 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1012 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1013 # could not understand a msg from its server the client cause a re-registering process
1014 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1015 "' to cause a re-registering of the client if necessary", 3);
1016 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1017 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1018 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1019 my $host_name = $hit->{'hostname'};
1020 my $host_key = $hit->{'hostkey'};
1021 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1022 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1023 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1024 }
1025 $error++;
1026 }
1029 my $header;
1030 my $target;
1031 my $source;
1032 my $done = 0;
1033 my $sql;
1034 my $res;
1036 # check whether this message should be processed here
1037 if ($error == 0) {
1038 $header = @{$msg_hash->{'header'}}[0];
1039 $target = @{$msg_hash->{'target'}}[0];
1040 $source = @{$msg_hash->{'source'}}[0];
1041 my $not_found_in_known_clients_db = 0;
1042 my $not_found_in_known_server_db = 0;
1043 my $not_found_in_foreign_clients_db = 0;
1044 my $local_address;
1045 my ($target_ip, $target_port) = split(':', $target);
1046 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1047 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1048 } else {
1049 $local_address = $server_address;
1050 }
1052 # target and source is equal to GOSA -> process here
1053 if (not $done) {
1054 if ($target eq "GOSA" && $source eq "GOSA") {
1055 $done = 1;
1056 }
1057 }
1059 # target is own address without forward_to_gosa-tag -> process here
1060 if (not $done) {
1061 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1062 $done = 1;
1063 if ($source eq "GOSA") {
1064 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1065 }
1066 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1067 }
1068 }
1070 # target is a client address in known_clients -> process here
1071 if (not $done) {
1072 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1073 $res = $known_clients_db->select_dbentry($sql);
1074 if (keys(%$res) > 0) {
1075 $done = 1;
1076 my $hostname = $res->{1}->{'hostname'};
1077 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1078 #print STDERR "target is a client address in known_clients -> process here\n";
1079 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1080 if ($source eq "GOSA") {
1081 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1082 }
1084 } else {
1085 $not_found_in_known_clients_db = 1;
1086 }
1087 }
1089 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1090 if (not $done) {
1091 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1092 my $gosa_at;
1093 my $gosa_session_id;
1094 if (($target eq $local_address) && (defined $forward_to_gosa)){
1095 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1096 if ($gosa_at ne $local_address) {
1097 $done = 1;
1098 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1099 }
1100 }
1101 }
1103 # if message should be processed here -> add message to incoming_db
1104 if ($done) {
1105 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1106 # so gosa-si-server knows how to process this kind of messages
1107 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1108 $module = "GosaPackages";
1109 }
1111 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1112 primkey=>[],
1113 headertag=>$header,
1114 targettag=>$target,
1115 xmlmessage=>&encode_base64($msg),
1116 timestamp=>&get_time,
1117 module=>$module,
1118 sessionid=>$session_id,
1119 } );
1120 }
1122 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1123 if (not $done) {
1124 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1125 my $gosa_at;
1126 my $gosa_session_id;
1127 if (($target eq $local_address) && (defined $forward_to_gosa)){
1128 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1129 if ($gosa_at eq $local_address) {
1130 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1131 if( defined $session_reference ) {
1132 $heap = $session_reference->get_heap();
1133 }
1134 if(exists $heap->{'client'}) {
1135 $msg = &encrypt_msg($msg, $GosaPackages_key);
1136 $heap->{'client'}->put($msg);
1137 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1138 }
1139 $done = 1;
1140 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1141 }
1142 }
1144 }
1146 # target is a client address in foreign_clients -> forward to registration server
1147 if (not $done) {
1148 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1149 $res = $foreign_clients_db->select_dbentry($sql);
1150 if (keys(%$res) > 0) {
1151 my $hostname = $res->{1}->{'hostname'};
1152 my ($host_ip, $host_port) = split(/:/, $hostname);
1153 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1154 my $regserver = $res->{1}->{'regserver'};
1155 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1156 my $res = $known_server_db->select_dbentry($sql);
1157 if (keys(%$res) > 0) {
1158 my $regserver_key = $res->{1}->{'hostkey'};
1159 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1160 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1161 if ($source eq "GOSA") {
1162 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1163 }
1164 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1165 }
1166 $done = 1;
1167 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1168 } else {
1169 $not_found_in_foreign_clients_db = 1;
1170 }
1171 }
1173 # target is a server address -> forward to server
1174 if (not $done) {
1175 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1176 $res = $known_server_db->select_dbentry($sql);
1177 if (keys(%$res) > 0) {
1178 my $hostkey = $res->{1}->{'hostkey'};
1180 if ($source eq "GOSA") {
1181 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1182 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1184 }
1186 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1187 $done = 1;
1188 #print STDERR "target is a server address -> forward to server\n";
1189 } else {
1190 $not_found_in_known_server_db = 1;
1191 }
1192 }
1195 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1196 if ( $not_found_in_foreign_clients_db
1197 && $not_found_in_known_server_db
1198 && $not_found_in_known_clients_db) {
1199 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1200 primkey=>[],
1201 headertag=>$header,
1202 targettag=>$target,
1203 xmlmessage=>&encode_base64($msg),
1204 timestamp=>&get_time,
1205 module=>$module,
1206 sessionid=>$session_id,
1207 } );
1208 $done = 1;
1209 }
1212 if (not $done) {
1213 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1214 if ($source eq "GOSA") {
1215 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1216 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1218 my $session_reference = $kernel->ID_id_to_session($session_id);
1219 if( defined $session_reference ) {
1220 $heap = $session_reference->get_heap();
1221 }
1222 if(exists $heap->{'client'}) {
1223 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1224 $heap->{'client'}->put($error_msg);
1225 }
1226 }
1227 }
1229 }
1231 return;
1232 }
1235 sub next_task {
1236 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1237 my $running_task = POE::Wheel::Run->new(
1238 Program => sub { process_task($session, $heap, $task) },
1239 StdioFilter => POE::Filter::Reference->new(),
1240 StdoutEvent => "task_result",
1241 StderrEvent => "task_debug",
1242 CloseEvent => "task_done",
1243 );
1244 $heap->{task}->{ $running_task->ID } = $running_task;
1245 }
1247 sub handle_task_result {
1248 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1249 my $client_answer = $result->{'answer'};
1250 if( $client_answer =~ s/session_id=(\d+)$// ) {
1251 my $session_id = $1;
1252 if( defined $session_id ) {
1253 my $session_reference = $kernel->ID_id_to_session($session_id);
1254 if( defined $session_reference ) {
1255 $heap = $session_reference->get_heap();
1256 }
1257 }
1259 if(exists $heap->{'client'}) {
1260 $heap->{'client'}->put($client_answer);
1261 }
1262 }
1263 $kernel->sig(CHLD => "child_reap");
1264 }
1266 sub handle_task_debug {
1267 my $result = $_[ARG0];
1268 print STDERR "$result\n";
1269 }
1271 sub handle_task_done {
1272 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1273 delete $heap->{task}->{$task_id};
1274 }
1276 sub process_task {
1277 no strict "refs";
1278 #CHECK: Not @_[...]?
1279 my ($session, $heap, $task) = @_;
1280 my $error = 0;
1281 my $answer_l;
1282 my ($answer_header, @answer_target_l, $answer_source);
1283 my $client_answer = "";
1285 # prepare all variables needed to process message
1286 #my $msg = $task->{'xmlmessage'};
1287 my $msg = &decode_base64($task->{'xmlmessage'});
1288 my $incoming_id = $task->{'id'};
1289 my $module = $task->{'module'};
1290 my $header = $task->{'headertag'};
1291 my $session_id = $task->{'sessionid'};
1292 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1293 my $source = @{$msg_hash->{'source'}}[0];
1295 # set timestamp of incoming client uptodate, so client will not
1296 # be deleted from known_clients because of expiration
1297 my $act_time = &get_time();
1298 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1299 my $res = $known_clients_db->exec_statement($sql);
1301 ######################
1302 # process incoming msg
1303 if( $error == 0) {
1304 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1305 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1306 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1308 if ( 0 < @{$answer_l} ) {
1309 my $answer_str = join("\n", @{$answer_l});
1310 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1311 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1312 }
1313 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1314 } else {
1315 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1316 }
1318 }
1319 if( !$answer_l ) { $error++ };
1321 ########
1322 # answer
1323 if( $error == 0 ) {
1325 foreach my $answer ( @{$answer_l} ) {
1326 # check outgoing msg to xml validity
1327 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1328 if( not defined $answer_hash ) { next; }
1330 $answer_header = @{$answer_hash->{'header'}}[0];
1331 @answer_target_l = @{$answer_hash->{'target'}};
1332 $answer_source = @{$answer_hash->{'source'}}[0];
1334 # deliver msg to all targets
1335 foreach my $answer_target ( @answer_target_l ) {
1337 # targets of msg are all gosa-si-clients in known_clients_db
1338 if( $answer_target eq "*" ) {
1339 # answer is for all clients
1340 my $sql_statement= "SELECT * FROM known_clients";
1341 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1342 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1343 my $host_name = $hit->{hostname};
1344 my $host_key = $hit->{hostkey};
1345 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1346 &update_jobdb_status_for_send_msgs($answer, $error);
1347 }
1348 }
1350 # targets of msg are all gosa-si-server in known_server_db
1351 elsif( $answer_target eq "KNOWN_SERVER" ) {
1352 # answer is for all server in known_server
1353 my $sql_statement= "SELECT * FROM $known_server_tn";
1354 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1355 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1356 my $host_name = $hit->{hostname};
1357 my $host_key = $hit->{hostkey};
1358 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1359 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1360 &update_jobdb_status_for_send_msgs($answer, $error);
1361 }
1362 }
1364 # target of msg is GOsa
1365 elsif( $answer_target eq "GOSA" ) {
1366 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1367 my $add_on = "";
1368 if( defined $session_id ) {
1369 $add_on = ".session_id=$session_id";
1370 }
1371 # answer is for GOSA and has to returned to connected client
1372 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1373 $client_answer = $gosa_answer.$add_on;
1374 }
1376 # target of msg is job queue at this host
1377 elsif( $answer_target eq "JOBDB") {
1378 $answer =~ /<header>(\S+)<\/header>/;
1379 my $header;
1380 if( defined $1 ) { $header = $1; }
1381 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1382 &update_jobdb_status_for_send_msgs($answer, $error);
1383 }
1385 # target of msg is a mac address
1386 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 ) {
1387 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1388 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1389 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1390 my $found_ip_flag = 0;
1391 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1392 my $host_name = $hit->{hostname};
1393 my $host_key = $hit->{hostkey};
1394 $answer =~ s/$answer_target/$host_name/g;
1395 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1396 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1397 &update_jobdb_status_for_send_msgs($answer, $error);
1398 $found_ip_flag++ ;
1399 }
1400 if( $found_ip_flag == 0) {
1401 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1402 }
1404 # answer is for one specific host
1405 } else {
1406 # get encrypt_key
1407 my $encrypt_key = &get_encrypt_key($answer_target);
1408 if( not defined $encrypt_key ) {
1409 # unknown target
1410 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1411 next;
1412 }
1413 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1414 &update_jobdb_status_for_send_msgs($answer, $error);
1415 }
1416 }
1417 }
1418 }
1420 my $filter = POE::Filter::Reference->new();
1421 my %result = (
1422 status => "seems ok to me",
1423 answer => $client_answer,
1424 );
1426 my $output = $filter->put( [ \%result ] );
1427 print @$output;
1430 }
1432 sub session_start {
1433 my ($kernel) = $_[KERNEL];
1434 $global_kernel = $kernel;
1435 $kernel->yield('register_at_foreign_servers');
1436 $kernel->yield('create_fai_server_db', $fai_server_tn );
1437 $kernel->yield('create_fai_release_db', $fai_release_tn );
1438 $kernel->yield('watch_for_next_tasks');
1439 $kernel->sig(USR1 => "sig_handler");
1440 $kernel->sig(USR2 => "recreate_packages_db");
1441 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1442 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1443 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1444 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1445 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1446 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1447 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1450 }
1453 sub watch_for_done_jobs {
1454 #CHECK: $heap for what?
1455 my ($kernel,$heap) = @_[KERNEL, HEAP];
1457 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1458 my $res = $job_db->select_dbentry( $sql_statement );
1460 while( my ($id, $hit) = each %{$res} ) {
1461 my $jobdb_id = $hit->{id};
1462 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1463 my $res = $job_db->del_dbentry($sql_statement);
1464 }
1466 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1467 }
1470 # if a job got an update or was modified anyway, send to all other si-server an update message
1471 # of this jobs
1472 sub watch_for_modified_jobs {
1473 my ($kernel,$heap) = @_[KERNEL, HEAP];
1475 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1476 my $res = $job_db->select_dbentry( $sql_statement );
1478 # if db contains no jobs which should be update, do nothing
1479 if (keys %$res != 0) {
1481 if ($job_synchronization eq "true") {
1482 # make out of the db result a gosa-si message
1483 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1485 # update all other SI-server
1486 &inform_all_other_si_server($update_msg);
1487 }
1489 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1490 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1491 $res = $job_db->update_dbentry($sql_statement);
1492 }
1494 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1495 }
1498 sub watch_for_new_jobs {
1499 if($watch_for_new_jobs_in_progress == 0) {
1500 $watch_for_new_jobs_in_progress = 1;
1501 my ($kernel,$heap) = @_[KERNEL, HEAP];
1503 # check gosa job quaeue for jobs with executable timestamp
1504 my $timestamp = &get_time();
1505 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1506 my $res = $job_db->exec_statement( $sql_statement );
1508 # Merge all new jobs that would do the same actions
1509 my @drops;
1510 my $hits;
1511 foreach my $hit (reverse @{$res} ) {
1512 my $macaddress= lc @{$hit}[8];
1513 my $headertag= @{$hit}[5];
1514 if(
1515 defined($hits->{$macaddress}) &&
1516 defined($hits->{$macaddress}->{$headertag}) &&
1517 defined($hits->{$macaddress}->{$headertag}[0])
1518 ) {
1519 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1520 }
1521 $hits->{$macaddress}->{$headertag}= $hit;
1522 }
1524 # Delete new jobs with a matching job in state 'processing'
1525 foreach my $macaddress (keys %{$hits}) {
1526 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1527 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1528 if(defined($jobdb_id)) {
1529 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1530 my $res = $job_db->exec_statement( $sql_statement );
1531 foreach my $hit (@{$res}) {
1532 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1533 }
1534 } else {
1535 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1536 }
1537 }
1538 }
1540 # Commit deletion
1541 $job_db->exec_statementlist(\@drops);
1543 # Look for new jobs that could be executed
1544 foreach my $macaddress (keys %{$hits}) {
1546 # Look if there is an executing job
1547 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1548 my $res = $job_db->exec_statement( $sql_statement );
1550 # Skip new jobs for host if there is a processing job
1551 if(defined($res) and defined @{$res}[0]) {
1552 next;
1553 }
1555 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1556 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1557 if(defined($jobdb_id)) {
1558 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1560 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1561 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1562 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1564 # expect macaddress is unique!!!!!!
1565 my $target = $res_hash->{1}->{hostname};
1567 # change header
1568 $job_msg =~ s/<header>job_/<header>gosa_/;
1570 # add sqlite_id
1571 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1573 $job_msg =~ /<header>(\S+)<\/header>/;
1574 my $header = $1 ;
1575 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1577 # update status in job queue to 'processing'
1578 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1579 my $res = $job_db->update_dbentry($sql_statement);
1580 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1582 # We don't want parallel processing
1583 last;
1584 }
1585 }
1586 }
1588 $watch_for_new_jobs_in_progress = 0;
1589 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1590 }
1591 }
1595 sub watch_for_new_messages {
1596 my ($kernel,$heap) = @_[KERNEL, HEAP];
1597 my @coll_user_msg; # collection list of outgoing messages
1599 # check messaging_db for new incoming messages with executable timestamp
1600 my $timestamp = &get_time();
1601 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1602 my $res = $messaging_db->exec_statement( $sql_statement );
1603 foreach my $hit (@{$res}) {
1605 # create outgoing messages
1606 my $message_to = @{$hit}[3];
1607 # translate message_to to plain login name
1608 my @message_to_l = split(/,/, $message_to);
1609 my %receiver_h;
1610 foreach my $receiver (@message_to_l) {
1611 if ($receiver =~ /^u_([\s\S]*)$/) {
1612 $receiver_h{$1} = 0;
1613 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1614 my $group_name = $1;
1615 # fetch all group members from ldap and add them to receiver hash
1616 my $ldap_handle = &get_ldap_handle();
1617 if (defined $ldap_handle) {
1618 my $mesg = $ldap_handle->search(
1619 base => $ldap_base,
1620 scope => 'sub',
1621 attrs => ['memberUid'],
1622 filter => "cn=$group_name",
1623 );
1624 if ($mesg->count) {
1625 my @entries = $mesg->entries;
1626 foreach my $entry (@entries) {
1627 my @receivers= $entry->get_value("memberUid");
1628 foreach my $receiver (@receivers) {
1629 $receiver_h{$1} = 0;
1630 }
1631 }
1632 }
1633 # translating errors ?
1634 if ($mesg->code) {
1635 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1636 }
1637 # ldap handle error ?
1638 } else {
1639 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1640 }
1641 } else {
1642 my $sbjct = &encode_base64(@{$hit}[1]);
1643 my $msg = &encode_base64(@{$hit}[7]);
1644 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1645 }
1646 }
1647 my @receiver_l = keys(%receiver_h);
1649 my $message_id = @{$hit}[0];
1651 #add each outgoing msg to messaging_db
1652 my $receiver;
1653 foreach $receiver (@receiver_l) {
1654 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1655 "VALUES ('".
1656 $message_id."', '". # id
1657 @{$hit}[1]."', '". # subject
1658 @{$hit}[2]."', '". # message_from
1659 $receiver."', '". # message_to
1660 "none"."', '". # flag
1661 "out"."', '". # direction
1662 @{$hit}[6]."', '". # delivery_time
1663 @{$hit}[7]."', '". # message
1664 $timestamp."'". # timestamp
1665 ")";
1666 &daemon_log("M DEBUG: $sql_statement", 1);
1667 my $res = $messaging_db->exec_statement($sql_statement);
1668 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1669 }
1671 # set incoming message to flag d=deliverd
1672 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1673 &daemon_log("M DEBUG: $sql_statement", 7);
1674 $res = $messaging_db->update_dbentry($sql_statement);
1675 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1676 }
1678 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1679 return;
1680 }
1682 sub watch_for_delivery_messages {
1683 my ($kernel, $heap) = @_[KERNEL, HEAP];
1685 # select outgoing messages
1686 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1687 #&daemon_log("0 DEBUG: $sql", 7);
1688 my $res = $messaging_db->exec_statement( $sql_statement );
1690 # build out msg for each usr
1691 foreach my $hit (@{$res}) {
1692 my $receiver = @{$hit}[3];
1693 my $msg_id = @{$hit}[0];
1694 my $subject = @{$hit}[1];
1695 my $message = @{$hit}[7];
1697 # resolve usr -> host where usr is logged in
1698 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1699 #&daemon_log("0 DEBUG: $sql", 7);
1700 my $res = $login_users_db->exec_statement($sql);
1702 # reciver is logged in nowhere
1703 if (not ref(@$res[0]) eq "ARRAY") { next; }
1705 my $send_succeed = 0;
1706 foreach my $hit (@$res) {
1707 my $receiver_host = @$hit[0];
1708 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1710 # fetch key to encrypt msg propperly for usr/host
1711 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1712 &daemon_log("0 DEBUG: $sql", 7);
1713 my $res = $known_clients_db->exec_statement($sql);
1715 # host is already down
1716 if (not ref(@$res[0]) eq "ARRAY") { next; }
1718 # host is on
1719 my $receiver_key = @{@{$res}[0]}[2];
1720 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1721 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1722 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1723 if ($error == 0 ) {
1724 $send_succeed++ ;
1725 }
1726 }
1728 if ($send_succeed) {
1729 # set outgoing msg at db to deliverd
1730 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1731 &daemon_log("0 DEBUG: $sql", 7);
1732 my $res = $messaging_db->exec_statement($sql);
1733 }
1734 }
1736 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1737 return;
1738 }
1741 sub watch_for_done_messages {
1742 my ($kernel,$heap) = @_[KERNEL, HEAP];
1744 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1745 #&daemon_log("0 DEBUG: $sql", 7);
1746 my $res = $messaging_db->exec_statement($sql);
1748 foreach my $hit (@{$res}) {
1749 my $msg_id = @{$hit}[0];
1751 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1752 #&daemon_log("0 DEBUG: $sql", 7);
1753 my $res = $messaging_db->exec_statement($sql);
1755 # not all usr msgs have been seen till now
1756 if ( ref(@$res[0]) eq "ARRAY") { next; }
1758 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1759 #&daemon_log("0 DEBUG: $sql", 7);
1760 $res = $messaging_db->exec_statement($sql);
1762 }
1764 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1765 return;
1766 }
1769 sub watch_for_old_known_clients {
1770 my ($kernel,$heap) = @_[KERNEL, HEAP];
1772 my $sql_statement = "SELECT * FROM $known_clients_tn";
1773 my $res = $known_clients_db->select_dbentry( $sql_statement );
1775 my $act_time = int(&get_time());
1777 while ( my ($hit_num, $hit) = each %$res) {
1778 my $expired_timestamp = int($hit->{'timestamp'});
1779 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1780 my $dt = DateTime->new( year => $1,
1781 month => $2,
1782 day => $3,
1783 hour => $4,
1784 minute => $5,
1785 second => $6,
1786 );
1788 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1789 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1790 if ($act_time > $expired_timestamp) {
1791 my $hostname = $hit->{'hostname'};
1792 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1793 my $del_res = $known_clients_db->exec_statement($del_sql);
1795 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1796 }
1798 }
1800 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1801 }
1804 sub watch_for_next_tasks {
1805 my ($kernel,$heap) = @_[KERNEL, HEAP];
1807 my $sql = "SELECT * FROM $incoming_tn";
1808 my $res = $incoming_db->select_dbentry($sql);
1810 while ( my ($hit_num, $hit) = each %$res) {
1811 my $headertag = $hit->{'headertag'};
1812 if ($headertag =~ /^answer_(\d+)/) {
1813 # do not start processing, this message is for a still running POE::Wheel
1814 next;
1815 }
1816 my $message_id = $hit->{'id'};
1817 $kernel->yield('next_task', $hit);
1819 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1820 my $res = $incoming_db->exec_statement($sql);
1821 }
1823 $kernel->delay_set('watch_for_next_tasks', 0.1);
1824 }
1827 sub get_ldap_handle {
1828 my ($session_id) = @_;
1829 my $heap;
1830 my $ldap_handle;
1832 if (not defined $session_id ) { $session_id = 0 };
1833 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1835 if ($session_id == 0) {
1836 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1837 $ldap_handle = Net::LDAP->new( $ldap_uri );
1838 $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!");
1840 } else {
1841 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1842 if( defined $session_reference ) {
1843 $heap = $session_reference->get_heap();
1844 }
1846 if (not defined $heap) {
1847 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1848 return;
1849 }
1851 # TODO: This "if" is nonsense, because it doesn't prove that the
1852 # used handle is still valid - or if we've to reconnect...
1853 #if (not exists $heap->{ldap_handle}) {
1854 $ldap_handle = Net::LDAP->new( $ldap_uri );
1855 $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!");
1856 $heap->{ldap_handle} = $ldap_handle;
1857 #}
1858 }
1859 return $ldap_handle;
1860 }
1863 sub change_fai_state {
1864 my ($st, $targets, $session_id) = @_;
1865 $session_id = 0 if not defined $session_id;
1866 # Set FAI state to localboot
1867 my %mapActions= (
1868 reboot => '',
1869 update => 'softupdate',
1870 localboot => 'localboot',
1871 reinstall => 'install',
1872 rescan => '',
1873 wake => '',
1874 memcheck => 'memcheck',
1875 sysinfo => 'sysinfo',
1876 install => 'install',
1877 );
1879 # Return if this is unknown
1880 if (!exists $mapActions{ $st }){
1881 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1882 return;
1883 }
1885 my $state= $mapActions{ $st };
1887 my $ldap_handle = &get_ldap_handle($session_id);
1888 if( defined($ldap_handle) ) {
1890 # Build search filter for hosts
1891 my $search= "(&(objectClass=GOhard)";
1892 foreach (@{$targets}){
1893 $search.= "(macAddress=$_)";
1894 }
1895 $search.= ")";
1897 # If there's any host inside of the search string, procress them
1898 if (!($search =~ /macAddress/)){
1899 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1900 return;
1901 }
1903 # Perform search for Unit Tag
1904 my $mesg = $ldap_handle->search(
1905 base => $ldap_base,
1906 scope => 'sub',
1907 attrs => ['dn', 'FAIstate', 'objectClass'],
1908 filter => "$search"
1909 );
1911 if ($mesg->count) {
1912 my @entries = $mesg->entries;
1913 if (0 == @entries) {
1914 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1915 }
1917 foreach my $entry (@entries) {
1918 # Only modify entry if it is not set to '$state'
1919 if ($entry->get_value("FAIstate") ne "$state"){
1920 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1921 my $result;
1922 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1923 if (exists $tmp{'FAIobject'}){
1924 if ($state eq ''){
1925 $result= $ldap_handle->modify($entry->dn, changes => [
1926 delete => [ FAIstate => [] ] ]);
1927 } else {
1928 $result= $ldap_handle->modify($entry->dn, changes => [
1929 replace => [ FAIstate => $state ] ]);
1930 }
1931 } elsif ($state ne ''){
1932 $result= $ldap_handle->modify($entry->dn, changes => [
1933 add => [ objectClass => 'FAIobject' ],
1934 add => [ FAIstate => $state ] ]);
1935 }
1937 # Errors?
1938 if ($result->code){
1939 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1940 }
1941 } else {
1942 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1943 }
1944 }
1945 } else {
1946 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1947 }
1949 # if no ldap handle defined
1950 } else {
1951 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1952 }
1954 return;
1955 }
1958 sub change_goto_state {
1959 my ($st, $targets, $session_id) = @_;
1960 $session_id = 0 if not defined $session_id;
1962 # Switch on or off?
1963 my $state= $st eq 'active' ? 'active': 'locked';
1965 my $ldap_handle = &get_ldap_handle($session_id);
1966 if( defined($ldap_handle) ) {
1968 # Build search filter for hosts
1969 my $search= "(&(objectClass=GOhard)";
1970 foreach (@{$targets}){
1971 $search.= "(macAddress=$_)";
1972 }
1973 $search.= ")";
1975 # If there's any host inside of the search string, procress them
1976 if (!($search =~ /macAddress/)){
1977 return;
1978 }
1980 # Perform search for Unit Tag
1981 my $mesg = $ldap_handle->search(
1982 base => $ldap_base,
1983 scope => 'sub',
1984 attrs => ['dn', 'gotoMode'],
1985 filter => "$search"
1986 );
1988 if ($mesg->count) {
1989 my @entries = $mesg->entries;
1990 foreach my $entry (@entries) {
1992 # Only modify entry if it is not set to '$state'
1993 if ($entry->get_value("gotoMode") ne $state){
1995 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1996 my $result;
1997 $result= $ldap_handle->modify($entry->dn, changes => [
1998 replace => [ gotoMode => $state ] ]);
2000 # Errors?
2001 if ($result->code){
2002 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2003 }
2005 }
2006 }
2007 } else {
2008 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2009 }
2011 }
2012 }
2015 sub run_recreate_packages_db {
2016 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2017 my $session_id = $session->ID;
2018 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2019 $kernel->yield('create_fai_release_db', $fai_release_tn);
2020 $kernel->yield('create_fai_server_db', $fai_server_tn);
2021 return;
2022 }
2025 sub run_create_fai_server_db {
2026 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2027 my $session_id = $session->ID;
2028 my $task = POE::Wheel::Run->new(
2029 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2030 StdoutEvent => "session_run_result",
2031 StderrEvent => "session_run_debug",
2032 CloseEvent => "session_run_done",
2033 );
2035 $heap->{task}->{ $task->ID } = $task;
2036 return;
2037 }
2040 sub create_fai_server_db {
2041 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2042 my $result;
2044 if (not defined $session_id) { $session_id = 0; }
2045 my $ldap_handle = &get_ldap_handle();
2046 if(defined($ldap_handle)) {
2047 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2048 my $mesg= $ldap_handle->search(
2049 base => $ldap_base,
2050 scope => 'sub',
2051 attrs => ['FAIrepository', 'gosaUnitTag'],
2052 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2053 );
2054 if($mesg->{'resultCode'} == 0 &&
2055 $mesg->count != 0) {
2056 foreach my $entry (@{$mesg->{entries}}) {
2057 if($entry->exists('FAIrepository')) {
2058 # Add an entry for each Repository configured for server
2059 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2060 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2061 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2062 $result= $fai_server_db->add_dbentry( {
2063 table => $table_name,
2064 primkey => ['server', 'release', 'tag'],
2065 server => $tmp_url,
2066 release => $tmp_release,
2067 sections => $tmp_sections,
2068 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2069 } );
2070 }
2071 }
2072 }
2073 }
2074 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2076 # TODO: Find a way to post the 'create_packages_list_db' event
2077 if(not defined($dont_create_packages_list)) {
2078 &create_packages_list_db(undef, undef, $session_id);
2079 }
2080 }
2082 $ldap_handle->disconnect;
2083 return $result;
2084 }
2087 sub run_create_fai_release_db {
2088 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2089 my $session_id = $session->ID;
2090 my $task = POE::Wheel::Run->new(
2091 Program => sub { &create_fai_release_db($table_name, $session_id) },
2092 StdoutEvent => "session_run_result",
2093 StderrEvent => "session_run_debug",
2094 CloseEvent => "session_run_done",
2095 );
2097 $heap->{task}->{ $task->ID } = $task;
2098 return;
2099 }
2102 sub create_fai_release_db {
2103 my ($table_name, $session_id) = @_;
2104 my $result;
2106 # used for logging
2107 if (not defined $session_id) { $session_id = 0; }
2109 my $ldap_handle = &get_ldap_handle();
2110 if(defined($ldap_handle)) {
2111 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2112 my $mesg= $ldap_handle->search(
2113 base => $ldap_base,
2114 scope => 'sub',
2115 attrs => [],
2116 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2117 );
2118 if($mesg->{'resultCode'} == 0 &&
2119 $mesg->count != 0) {
2120 # Walk through all possible FAI container ou's
2121 my @sql_list;
2122 my $timestamp= &get_time();
2123 foreach my $ou (@{$mesg->{entries}}) {
2124 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2125 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2126 my @tmp_array=get_fai_release_entries($tmp_classes);
2127 if(@tmp_array) {
2128 foreach my $entry (@tmp_array) {
2129 if(defined($entry) && ref($entry) eq 'HASH') {
2130 my $sql=
2131 "INSERT INTO $table_name "
2132 ."(timestamp, release, class, type, state) VALUES ("
2133 .$timestamp.","
2134 ."'".$entry->{'release'}."',"
2135 ."'".$entry->{'class'}."',"
2136 ."'".$entry->{'type'}."',"
2137 ."'".$entry->{'state'}."')";
2138 push @sql_list, $sql;
2139 }
2140 }
2141 }
2142 }
2143 }
2145 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2146 if(@sql_list) {
2147 unshift @sql_list, "VACUUM";
2148 unshift @sql_list, "DELETE FROM $table_name";
2149 $fai_release_db->exec_statementlist(\@sql_list);
2150 }
2151 daemon_log("$session_id DEBUG: Done with inserting",7);
2152 }
2153 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2154 }
2155 $ldap_handle->disconnect;
2156 return $result;
2157 }
2159 sub get_fai_types {
2160 my $tmp_classes = shift || return undef;
2161 my @result;
2163 foreach my $type(keys %{$tmp_classes}) {
2164 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2165 my $entry = {
2166 type => $type,
2167 state => $tmp_classes->{$type}[0],
2168 };
2169 push @result, $entry;
2170 }
2171 }
2173 return @result;
2174 }
2176 sub get_fai_state {
2177 my $result = "";
2178 my $tmp_classes = shift || return $result;
2180 foreach my $type(keys %{$tmp_classes}) {
2181 if(defined($tmp_classes->{$type}[0])) {
2182 $result = $tmp_classes->{$type}[0];
2184 # State is equal for all types in class
2185 last;
2186 }
2187 }
2189 return $result;
2190 }
2192 sub resolve_fai_classes {
2193 my ($fai_base, $ldap_handle, $session_id) = @_;
2194 if (not defined $session_id) { $session_id = 0; }
2195 my $result;
2196 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2197 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2198 my $fai_classes;
2200 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2201 my $mesg= $ldap_handle->search(
2202 base => $fai_base,
2203 scope => 'sub',
2204 attrs => ['cn','objectClass','FAIstate'],
2205 filter => $fai_filter,
2206 );
2207 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2209 if($mesg->{'resultCode'} == 0 &&
2210 $mesg->count != 0) {
2211 foreach my $entry (@{$mesg->{entries}}) {
2212 if($entry->exists('cn')) {
2213 my $tmp_dn= $entry->dn();
2215 # Skip classname and ou dn parts for class
2216 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2218 # Skip classes without releases
2219 if((!defined($tmp_release)) || length($tmp_release)==0) {
2220 next;
2221 }
2223 my $tmp_cn= $entry->get_value('cn');
2224 my $tmp_state= $entry->get_value('FAIstate');
2226 my $tmp_type;
2227 # Get FAI type
2228 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2229 if(grep $_ eq $oclass, @possible_fai_classes) {
2230 $tmp_type= $oclass;
2231 last;
2232 }
2233 }
2235 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2236 # A Subrelease
2237 my @sub_releases = split(/,/, $tmp_release);
2239 # Walk through subreleases and build hash tree
2240 my $hash;
2241 while(my $tmp_sub_release = pop @sub_releases) {
2242 $hash .= "\{'$tmp_sub_release'\}->";
2243 }
2244 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2245 } else {
2246 # A branch, no subrelease
2247 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2248 }
2249 } elsif (!$entry->exists('cn')) {
2250 my $tmp_dn= $entry->dn();
2251 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2253 # Skip classes without releases
2254 if((!defined($tmp_release)) || length($tmp_release)==0) {
2255 next;
2256 }
2258 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2259 # A Subrelease
2260 my @sub_releases= split(/,/, $tmp_release);
2262 # Walk through subreleases and build hash tree
2263 my $hash;
2264 while(my $tmp_sub_release = pop @sub_releases) {
2265 $hash .= "\{'$tmp_sub_release'\}->";
2266 }
2267 # Remove the last two characters
2268 chop($hash);
2269 chop($hash);
2271 eval('$fai_classes->'.$hash.'= {}');
2272 } else {
2273 # A branch, no subrelease
2274 if(!exists($fai_classes->{$tmp_release})) {
2275 $fai_classes->{$tmp_release} = {};
2276 }
2277 }
2278 }
2279 }
2281 # The hash is complete, now we can honor the copy-on-write based missing entries
2282 foreach my $release (keys %$fai_classes) {
2283 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2284 }
2285 }
2286 return $result;
2287 }
2289 sub apply_fai_inheritance {
2290 my $fai_classes = shift || return {};
2291 my $tmp_classes;
2293 # Get the classes from the branch
2294 foreach my $class (keys %{$fai_classes}) {
2295 # Skip subreleases
2296 if($class =~ /^ou=.*$/) {
2297 next;
2298 } else {
2299 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2300 }
2301 }
2303 # Apply to each subrelease
2304 foreach my $subrelease (keys %{$fai_classes}) {
2305 if($subrelease =~ /ou=/) {
2306 foreach my $tmp_class (keys %{$tmp_classes}) {
2307 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2308 $fai_classes->{$subrelease}->{$tmp_class} =
2309 deep_copy($tmp_classes->{$tmp_class});
2310 } else {
2311 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2312 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2313 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2314 deep_copy($tmp_classes->{$tmp_class}->{$type});
2315 }
2316 }
2317 }
2318 }
2319 }
2320 }
2322 # Find subreleases in deeper levels
2323 foreach my $subrelease (keys %{$fai_classes}) {
2324 if($subrelease =~ /ou=/) {
2325 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2326 if($subsubrelease =~ /ou=/) {
2327 apply_fai_inheritance($fai_classes->{$subrelease});
2328 }
2329 }
2330 }
2331 }
2333 return $fai_classes;
2334 }
2336 sub get_fai_release_entries {
2337 my $tmp_classes = shift || return;
2338 my $parent = shift || "";
2339 my @result = shift || ();
2341 foreach my $entry (keys %{$tmp_classes}) {
2342 if(defined($entry)) {
2343 if($entry =~ /^ou=.*$/) {
2344 my $release_name = $entry;
2345 $release_name =~ s/ou=//g;
2346 if(length($parent)>0) {
2347 $release_name = $parent."/".$release_name;
2348 }
2349 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2350 foreach my $bufentry(@bufentries) {
2351 push @result, $bufentry;
2352 }
2353 } else {
2354 my @types = get_fai_types($tmp_classes->{$entry});
2355 foreach my $type (@types) {
2356 push @result,
2357 {
2358 'class' => $entry,
2359 'type' => $type->{'type'},
2360 'release' => $parent,
2361 'state' => $type->{'state'},
2362 };
2363 }
2364 }
2365 }
2366 }
2368 return @result;
2369 }
2371 sub deep_copy {
2372 my $this = shift;
2373 if (not ref $this) {
2374 $this;
2375 } elsif (ref $this eq "ARRAY") {
2376 [map deep_copy($_), @$this];
2377 } elsif (ref $this eq "HASH") {
2378 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2379 } else { die "what type is $_?" }
2380 }
2383 sub session_run_result {
2384 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2385 $kernel->sig(CHLD => "child_reap");
2386 }
2388 sub session_run_debug {
2389 my $result = $_[ARG0];
2390 print STDERR "$result\n";
2391 }
2393 sub session_run_done {
2394 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2395 delete $heap->{task}->{$task_id};
2396 }
2399 sub create_sources_list {
2400 my $session_id = shift;
2401 my $ldap_handle = &main::get_ldap_handle;
2402 my $result="/tmp/gosa_si_tmp_sources_list";
2404 # Remove old file
2405 if(stat($result)) {
2406 unlink($result);
2407 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2408 }
2410 my $fh;
2411 open($fh, ">$result");
2412 if (not defined $fh) {
2413 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2414 return undef;
2415 }
2416 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2417 my $mesg=$ldap_handle->search(
2418 base => $main::ldap_server_dn,
2419 scope => 'base',
2420 attrs => 'FAIrepository',
2421 filter => 'objectClass=FAIrepositoryServer'
2422 );
2423 if($mesg->count) {
2424 foreach my $entry(@{$mesg->{'entries'}}) {
2425 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2426 my ($server, $tag, $release, $sections)= split /\|/, $value;
2427 my $line = "deb $server $release";
2428 $sections =~ s/,/ /g;
2429 $line.= " $sections";
2430 print $fh $line."\n";
2431 }
2432 }
2433 }
2434 } else {
2435 if (defined $main::ldap_server_dn){
2436 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2437 } else {
2438 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2439 }
2440 }
2441 close($fh);
2443 return $result;
2444 }
2447 sub run_create_packages_list_db {
2448 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2449 my $session_id = $session->ID;
2451 my $task = POE::Wheel::Run->new(
2452 Priority => +20,
2453 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2454 StdoutEvent => "session_run_result",
2455 StderrEvent => "session_run_debug",
2456 CloseEvent => "session_run_done",
2457 );
2458 $heap->{task}->{ $task->ID } = $task;
2459 }
2462 sub create_packages_list_db {
2463 my ($ldap_handle, $sources_file, $session_id) = @_;
2465 # it should not be possible to trigger a recreation of packages_list_db
2466 # while packages_list_db is under construction, so set flag packages_list_under_construction
2467 # which is tested befor recreation can be started
2468 if (-r $packages_list_under_construction) {
2469 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2470 return;
2471 } else {
2472 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2473 # set packages_list_under_construction to true
2474 system("touch $packages_list_under_construction");
2475 @packages_list_statements=();
2476 }
2478 if (not defined $session_id) { $session_id = 0; }
2479 if (not defined $ldap_handle) {
2480 $ldap_handle= &get_ldap_handle();
2482 if (not defined $ldap_handle) {
2483 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2484 unlink($packages_list_under_construction);
2485 return;
2486 }
2487 }
2488 if (not defined $sources_file) {
2489 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2490 $sources_file = &create_sources_list($session_id);
2491 }
2493 if (not defined $sources_file) {
2494 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2495 unlink($packages_list_under_construction);
2496 return;
2497 }
2499 my $line;
2501 open(CONFIG, "<$sources_file") or do {
2502 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2503 unlink($packages_list_under_construction);
2504 return;
2505 };
2507 # Read lines
2508 while ($line = <CONFIG>){
2509 # Unify
2510 chop($line);
2511 $line =~ s/^\s+//;
2512 $line =~ s/^\s+/ /;
2514 # Strip comments
2515 $line =~ s/#.*$//g;
2517 # Skip empty lines
2518 if ($line =~ /^\s*$/){
2519 next;
2520 }
2522 # Interpret deb line
2523 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2524 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2525 my $section;
2526 foreach $section (split(' ', $sections)){
2527 &parse_package_info( $baseurl, $dist, $section, $session_id );
2528 }
2529 }
2530 }
2532 close (CONFIG);
2534 find(\&cleanup_and_extract, keys( %repo_dirs ));
2535 &main::strip_packages_list_statements();
2536 unshift @packages_list_statements, "VACUUM";
2537 $packages_list_db->exec_statementlist(\@packages_list_statements);
2538 unlink($packages_list_under_construction);
2539 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2540 return;
2541 }
2543 # This function should do some intensive task to minimize the db-traffic
2544 sub strip_packages_list_statements {
2545 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2546 my @new_statement_list=();
2547 my $hash;
2548 my $insert_hash;
2549 my $update_hash;
2550 my $delete_hash;
2551 my $local_timestamp=get_time();
2553 foreach my $existing_entry (@existing_entries) {
2554 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2555 }
2557 foreach my $statement (@packages_list_statements) {
2558 if($statement =~ /^INSERT/i) {
2559 # Assign the values from the insert statement
2560 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2561 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2562 if(exists($hash->{$distribution}->{$package}->{$version})) {
2563 # If section or description has changed, update the DB
2564 if(
2565 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2566 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2567 ) {
2568 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2569 }
2570 } else {
2571 # Insert a non-existing entry to db
2572 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2573 }
2574 } elsif ($statement =~ /^UPDATE/i) {
2575 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2576 /^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;
2577 foreach my $distribution (keys %{$hash}) {
2578 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2579 # update the insertion hash to execute only one query per package (insert instead insert+update)
2580 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2581 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2582 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2583 my $section;
2584 my $description;
2585 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2586 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2587 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2588 }
2589 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2590 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2591 }
2592 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2593 }
2594 }
2595 }
2596 }
2597 }
2599 # TODO: Check for orphaned entries
2601 # unroll the insert_hash
2602 foreach my $distribution (keys %{$insert_hash}) {
2603 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2604 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2605 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2606 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2607 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2608 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2609 ."'$local_timestamp')";
2610 }
2611 }
2612 }
2614 # unroll the update hash
2615 foreach my $distribution (keys %{$update_hash}) {
2616 foreach my $package (keys %{$update_hash->{$distribution}}) {
2617 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2618 my $set = "";
2619 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2620 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2621 }
2622 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2623 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2624 }
2625 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2626 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2627 }
2628 if(defined($set) and length($set) > 0) {
2629 $set .= "timestamp = '$local_timestamp'";
2630 } else {
2631 next;
2632 }
2633 push @new_statement_list,
2634 "UPDATE $main::packages_list_tn SET $set WHERE"
2635 ." distribution = '$distribution'"
2636 ." AND package = '$package'"
2637 ." AND version = '$version'";
2638 }
2639 }
2640 }
2642 @packages_list_statements = @new_statement_list;
2643 }
2646 sub parse_package_info {
2647 my ($baseurl, $dist, $section, $session_id)= @_;
2648 my ($package);
2649 if (not defined $session_id) { $session_id = 0; }
2650 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2651 $repo_dirs{ "${repo_path}/pool" } = 1;
2653 foreach $package ("Packages.gz"){
2654 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2655 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2656 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2657 }
2659 }
2662 sub get_package {
2663 my ($url, $dest, $session_id)= @_;
2664 if (not defined $session_id) { $session_id = 0; }
2666 my $tpath = dirname($dest);
2667 -d "$tpath" || mkpath "$tpath";
2669 # This is ugly, but I've no time to take a look at "how it works in perl"
2670 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2671 system("gunzip -cd '$dest' > '$dest.in'");
2672 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2673 unlink($dest);
2674 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2675 } else {
2676 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2677 }
2678 return 0;
2679 }
2682 sub parse_package {
2683 my ($path, $dist, $srv_path, $session_id)= @_;
2684 if (not defined $session_id) { $session_id = 0;}
2685 my ($package, $version, $section, $description);
2686 my $PACKAGES;
2687 my $timestamp = &get_time();
2689 if(not stat("$path.in")) {
2690 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2691 return;
2692 }
2694 open($PACKAGES, "<$path.in");
2695 if(not defined($PACKAGES)) {
2696 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2697 return;
2698 }
2700 # Read lines
2701 while (<$PACKAGES>){
2702 my $line = $_;
2703 # Unify
2704 chop($line);
2706 # Use empty lines as a trigger
2707 if ($line =~ /^\s*$/){
2708 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2709 push(@packages_list_statements, $sql);
2710 $package = "none";
2711 $version = "none";
2712 $section = "none";
2713 $description = "none";
2714 next;
2715 }
2717 # Trigger for package name
2718 if ($line =~ /^Package:\s/){
2719 ($package)= ($line =~ /^Package: (.*)$/);
2720 next;
2721 }
2723 # Trigger for version
2724 if ($line =~ /^Version:\s/){
2725 ($version)= ($line =~ /^Version: (.*)$/);
2726 next;
2727 }
2729 # Trigger for description
2730 if ($line =~ /^Description:\s/){
2731 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2732 next;
2733 }
2735 # Trigger for section
2736 if ($line =~ /^Section:\s/){
2737 ($section)= ($line =~ /^Section: (.*)$/);
2738 next;
2739 }
2741 # Trigger for filename
2742 if ($line =~ /^Filename:\s/){
2743 my ($filename) = ($line =~ /^Filename: (.*)$/);
2744 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2745 next;
2746 }
2747 }
2749 close( $PACKAGES );
2750 unlink( "$path.in" );
2751 }
2754 sub store_fileinfo {
2755 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2757 my %fileinfo = (
2758 'package' => $package,
2759 'dist' => $dist,
2760 'version' => $vers,
2761 );
2763 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2764 }
2767 sub cleanup_and_extract {
2768 my $fileinfo = $repo_files{ $File::Find::name };
2770 if( defined $fileinfo ) {
2772 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2773 my $sql;
2774 my $package = $fileinfo->{ 'package' };
2775 my $newver = $fileinfo->{ 'version' };
2777 mkpath($dir);
2778 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2780 if( -f "$dir/DEBIAN/templates" ) {
2782 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2784 my $tmpl= "";
2785 {
2786 local $/=undef;
2787 open FILE, "$dir/DEBIAN/templates";
2788 $tmpl = &encode_base64(<FILE>);
2789 close FILE;
2790 }
2791 rmtree("$dir/DEBIAN/templates");
2793 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2794 push @packages_list_statements, $sql;
2795 }
2796 }
2798 return;
2799 }
2802 sub register_at_foreign_servers {
2803 my ($kernel) = $_[KERNEL];
2805 # hole alle bekannten server aus known_server_db
2806 my $server_sql = "SELECT * FROM $known_server_tn";
2807 my $server_res = $known_server_db->exec_statement($server_sql);
2809 # no entries in known_server_db
2810 if (not ref(@$server_res[0]) eq "ARRAY") {
2811 # TODO
2812 }
2814 # detect already connected clients
2815 my $client_sql = "SELECT * FROM $known_clients_tn";
2816 my $client_res = $known_clients_db->exec_statement($client_sql);
2818 # send my server details to all other gosa-si-server within the network
2819 foreach my $hit (@$server_res) {
2820 my $hostname = @$hit[0];
2821 my $hostkey = &create_passwd;
2823 # add already connected clients to registration message
2824 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2825 &add_content2xml_hash($myhash, 'key', $hostkey);
2826 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2828 # build registration message and send it
2829 my $foreign_server_msg = &create_xml_string($myhash);
2830 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2831 }
2833 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2834 return;
2835 }
2838 #==== MAIN = main ==============================================================
2839 # parse commandline options
2840 Getopt::Long::Configure( "bundling" );
2841 GetOptions("h|help" => \&usage,
2842 "c|config=s" => \$cfg_file,
2843 "f|foreground" => \$foreground,
2844 "v|verbose+" => \$verbose,
2845 "no-arp+" => \$no_arp,
2846 );
2848 # read and set config parameters
2849 &check_cmdline_param ;
2850 &read_configfile($cfg_file, %cfg_defaults);
2851 &check_pid;
2853 $SIG{CHLD} = 'IGNORE';
2855 # forward error messages to logfile
2856 if( ! $foreground ) {
2857 open( STDIN, '+>/dev/null' );
2858 open( STDOUT, '+>&STDIN' );
2859 open( STDERR, '+>&STDIN' );
2860 }
2862 # Just fork, if we are not in foreground mode
2863 if( ! $foreground ) {
2864 chdir '/' or die "Can't chdir to /: $!";
2865 $pid = fork;
2866 setsid or die "Can't start a new session: $!";
2867 umask 0;
2868 } else {
2869 $pid = $$;
2870 }
2872 # Do something useful - put our PID into the pid_file
2873 if( 0 != $pid ) {
2874 open( LOCK_FILE, ">$pid_file" );
2875 print LOCK_FILE "$pid\n";
2876 close( LOCK_FILE );
2877 if( !$foreground ) {
2878 exit( 0 )
2879 };
2880 }
2882 # parse head url and revision from svn
2883 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2884 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2885 $server_headURL = defined $1 ? $1 : 'unknown' ;
2886 $server_revision = defined $2 ? $2 : 'unknown' ;
2887 if ($server_headURL =~ /\/tag\// ||
2888 $server_headURL =~ /\/branches\// ) {
2889 $server_status = "stable";
2890 } else {
2891 $server_status = "developmental" ;
2892 }
2895 daemon_log(" ", 1);
2896 daemon_log("$0 started!", 1);
2897 daemon_log("status: $server_status", 1);
2898 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2900 # connect to incoming_db
2901 unlink($incoming_file_name);
2902 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2903 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2905 # connect to gosa-si job queue
2906 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2907 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2909 # connect to known_clients_db
2910 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2911 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2913 # connect to foreign_clients_db
2914 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2915 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2917 # connect to known_server_db
2918 unlink($known_server_file_name);
2919 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2920 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2922 # connect to login_usr_db
2923 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2924 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2926 # connect to fai_server_db and fai_release_db
2927 unlink($fai_server_file_name);
2928 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2929 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2931 unlink($fai_release_file_name);
2932 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2933 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2935 # connect to packages_list_db
2936 #unlink($packages_list_file_name);
2937 unlink($packages_list_under_construction);
2938 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2939 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2941 # connect to messaging_db
2942 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2943 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2946 # create xml object used for en/decrypting
2947 $xml = new XML::Simple();
2950 # foreign servers
2951 my @foreign_server_list;
2953 # add foreign server from cfg file
2954 if ($foreign_server_string ne "") {
2955 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2956 foreach my $foreign_server (@cfg_foreign_server_list) {
2957 push(@foreign_server_list, $foreign_server);
2958 }
2959 }
2961 # add foreign server from dns
2962 my @tmp_servers;
2963 if ( !$server_domain) {
2964 # Try our DNS Searchlist
2965 for my $domain(get_dns_domains()) {
2966 chomp($domain);
2967 my @tmp_domains= &get_server_addresses($domain);
2968 if(@tmp_domains) {
2969 for my $tmp_server(@tmp_domains) {
2970 push @tmp_servers, $tmp_server;
2971 }
2972 }
2973 }
2974 if(@tmp_servers && length(@tmp_servers)==0) {
2975 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2976 }
2977 } else {
2978 @tmp_servers = &get_server_addresses($server_domain);
2979 if( 0 == @tmp_servers ) {
2980 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2981 }
2982 }
2983 foreach my $server (@tmp_servers) {
2984 unshift(@foreign_server_list, $server);
2985 }
2986 # eliminate duplicate entries
2987 @foreign_server_list = &del_doubles(@foreign_server_list);
2988 my $all_foreign_server = join(", ", @foreign_server_list);
2989 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2991 # add all found foreign servers to known_server
2992 my $act_timestamp = &get_time();
2993 foreach my $foreign_server (@foreign_server_list) {
2995 # do not add myself to known_server_db
2996 if (&is_local($foreign_server)) { next; }
2997 ######################################
2999 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3000 primkey=>['hostname'],
3001 hostname=>$foreign_server,
3002 status=>'not_jet_registered',
3003 hostkey=>"none",
3004 timestamp=>$act_timestamp,
3005 } );
3006 }
3009 # import all modules
3010 &import_modules;
3011 # check wether all modules are gosa-si valid passwd check
3012 &password_check;
3015 POE::Component::Server::TCP->new(
3016 Alias => "TCP_SERVER",
3017 Port => $server_port,
3018 ClientInput => sub {
3019 my ($kernel, $input) = @_[KERNEL, ARG0];
3020 push(@tasks, $input);
3021 push(@msgs_to_decrypt, $input);
3022 $kernel->yield("msg_to_decrypt");
3023 },
3024 InlineStates => {
3025 msg_to_decrypt => \&msg_to_decrypt,
3026 next_task => \&next_task,
3027 task_result => \&handle_task_result,
3028 task_done => \&handle_task_done,
3029 task_debug => \&handle_task_debug,
3030 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3031 }
3032 );
3034 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3036 # create session for repeatedly checking the job queue for jobs
3037 POE::Session->create(
3038 inline_states => {
3039 _start => \&session_start,
3040 register_at_foreign_servers => \®ister_at_foreign_servers,
3041 sig_handler => \&sig_handler,
3042 next_task => \&next_task,
3043 task_result => \&handle_task_result,
3044 task_done => \&handle_task_done,
3045 task_debug => \&handle_task_debug,
3046 watch_for_next_tasks => \&watch_for_next_tasks,
3047 watch_for_new_messages => \&watch_for_new_messages,
3048 watch_for_delivery_messages => \&watch_for_delivery_messages,
3049 watch_for_done_messages => \&watch_for_done_messages,
3050 watch_for_new_jobs => \&watch_for_new_jobs,
3051 watch_for_modified_jobs => \&watch_for_modified_jobs,
3052 watch_for_done_jobs => \&watch_for_done_jobs,
3053 watch_for_old_known_clients => \&watch_for_old_known_clients,
3054 create_packages_list_db => \&run_create_packages_list_db,
3055 create_fai_server_db => \&run_create_fai_server_db,
3056 create_fai_release_db => \&run_create_fai_release_db,
3057 recreate_packages_db => \&run_recreate_packages_db,
3058 session_run_result => \&session_run_result,
3059 session_run_debug => \&session_run_debug,
3060 session_run_done => \&session_run_done,
3061 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3062 }
3063 );
3066 POE::Kernel->run();
3067 exit;