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);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
82 # Variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file,
84 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
85 $arp_activ, $gosa_unit_tag,
86 $GosaPackages_key, $gosa_timeout,
87 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89 $arp_enabled, $arp_interface,
90 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91 $new_systems_ou,
92 );
94 # additional variable which should be globaly accessable
95 our $server_address;
96 our $server_mac_address;
97 our $gosa_address;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 our $known_modules;
104 our $root_uid;
105 our $adm_gid;
108 # specifies the verbosity of the daemon_log
109 $verbose = 0 ;
111 # if foreground is not null, script will be not forked to background
112 $foreground = 0 ;
114 # specifies the timeout seconds while checking the online status of a registrating client
115 $ping_timeout = 5;
117 $no_arp = 0;
118 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
119 my @packages_list_statements;
120 my $watch_for_new_jobs_in_progress = 0;
122 # holds all incoming decrypted messages
123 our $incoming_db;
124 our $incoming_tn = 'incoming';
125 my $incoming_file_name;
126 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
127 "timestamp DEFAULT 'none'",
128 "headertag DEFAULT 'none'",
129 "targettag DEFAULT 'none'",
130 "xmlmessage DEFAULT 'none'",
131 "module DEFAULT 'none'",
132 "sessionid DEFAULT '0'",
133 );
135 # holds all gosa jobs
136 our $job_db;
137 our $job_queue_tn = 'jobs';
138 my $job_queue_file_name;
139 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
140 "timestamp DEFAULT 'none'",
141 "status DEFAULT 'none'",
142 "result DEFAULT 'none'",
143 "progress DEFAULT 'none'",
144 "headertag DEFAULT 'none'",
145 "targettag DEFAULT 'none'",
146 "xmlmessage DEFAULT 'none'",
147 "macaddress DEFAULT 'none'",
148 "plainname DEFAULT 'none'",
149 "siserver DEFAULT 'none'",
150 "modified DEFAULT '0'",
151 );
153 # holds all other gosa-si-server
154 our $known_server_db;
155 our $known_server_tn = "known_server";
156 my $known_server_file_name;
157 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
159 # holds all registrated clients
160 our $known_clients_db;
161 our $known_clients_tn = "known_clients";
162 my $known_clients_file_name;
163 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
165 # holds all registered clients at a foreign server
166 our $foreign_clients_db;
167 our $foreign_clients_tn = "foreign_clients";
168 my $foreign_clients_file_name;
169 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
171 # holds all logged in user at each client
172 our $login_users_db;
173 our $login_users_tn = "login_users";
174 my $login_users_file_name;
175 my @login_users_col_names = ("client", "user", "timestamp", "regserver");
177 # holds all fai server, the debian release and tag
178 our $fai_server_db;
179 our $fai_server_tn = "fai_server";
180 my $fai_server_file_name;
181 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
183 our $fai_release_db;
184 our $fai_release_tn = "fai_release";
185 my $fai_release_file_name;
186 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
188 # holds all packages available from different repositories
189 our $packages_list_db;
190 our $packages_list_tn = "packages_list";
191 my $packages_list_file_name;
192 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
193 my $outdir = "/tmp/packages_list_db";
194 my $arch = "i386";
196 # holds all messages which should be delivered to a user
197 our $messaging_db;
198 our $messaging_tn = "messaging";
199 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
200 "flag", "direction", "delivery_time", "message", "timestamp" );
201 my $messaging_file_name;
203 # path to directory to store client install log files
204 our $client_fai_log_dir = "/var/log/fai";
206 # queue which stores taskes until one of the $max_children children are ready to process the task
207 my @tasks = qw();
208 my @msgs_to_decrypt = qw();
209 my $max_children = 2;
212 # loop delay for job queue to look for opsi jobs
213 my $job_queue_opsi_delay = 10;
214 our $opsi_client;
215 our $opsi_url;
217 # Lifetime of logged in user information. If no update information comes after n seconds,
218 # the user is expeceted to be no longer logged in or the host is no longer running. Because
219 # of this, the user is deleted from login_users_db
220 our $logged_in_user_date_of_expiry = 600;
223 %cfg_defaults = (
224 "general" => {
225 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
226 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
227 },
228 "server" => {
229 "ip" => [\$server_ip, "0.0.0.0"],
230 "port" => [\$server_port, "20081"],
231 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
232 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
233 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
234 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
235 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
236 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
237 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
238 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
239 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
240 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
241 "repo-path" => [\$repo_path, '/srv/www/repository'],
242 "ldap-uri" => [\$ldap_uri, ""],
243 "ldap-base" => [\$ldap_base, ""],
244 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
245 "ldap-admin-password" => [\$ldap_admin_password, ""],
246 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
247 "max-clients" => [\$max_clients, 10],
248 "wol-password" => [\$wake_on_lan_passwd, ""],
249 },
250 "GOsaPackages" => {
251 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
252 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
253 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
254 "key" => [\$GosaPackages_key, "none"],
255 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
256 },
257 "ClientPackages" => {
258 "key" => [\$ClientPackages_key, "none"],
259 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
260 },
261 "ServerPackages"=> {
262 "address" => [\$foreign_server_string, ""],
263 "dns-lookup" => [\$dns_lookup, "true"],
264 "domain" => [\$server_domain, ""],
265 "key" => [\$ServerPackages_key, "none"],
266 "key-lifetime" => [\$foreign_servers_register_delay, 120],
267 "job-synchronization-enabled" => [\$job_synchronization, "true"],
268 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
269 },
270 "ArpHandler" => {
271 "enabled" => [\$arp_enabled, "true"],
272 "interface" => [\$arp_interface, "all"],
273 },
274 "Opsi" => {
275 "enabled" => [\$opsi_enabled, "false"],
276 "server" => [\$opsi_server, "localhost"],
277 "admin" => [\$opsi_admin, "opsi-admin"],
278 "password" => [\$opsi_password, "secret"],
279 },
281 );
284 #=== FUNCTION ================================================================
285 # NAME: usage
286 # PARAMETERS: nothing
287 # RETURNS: nothing
288 # DESCRIPTION: print out usage text to STDERR
289 #===============================================================================
290 sub usage {
291 print STDERR << "EOF" ;
292 usage: $prg [-hvf] [-c config]
294 -h : this (help) message
295 -c <file> : config file
296 -f : foreground, process will not be forked to background
297 -v : be verbose (multiple to increase verbosity)
298 -no-arp : starts $prg without connection to arp module
300 EOF
301 print "\n" ;
302 }
305 #=== FUNCTION ================================================================
306 # NAME: logging
307 # PARAMETERS: level - string - default 'info'
308 # msg - string -
309 # facility - string - default 'LOG_DAEMON'
310 # RETURNS: nothing
311 # DESCRIPTION: function for logging
312 #===============================================================================
313 sub daemon_log {
314 # log into log_file
315 my( $msg, $level ) = @_;
316 if(not defined $msg) { return }
317 if(not defined $level) { $level = 1 }
318 if(defined $log_file){
319 open(LOG_HANDLE, ">>$log_file");
320 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
321 print STDERR "cannot open $log_file: $!";
322 return
323 }
324 chomp($msg);
325 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
326 if($level <= $verbose){
327 my ($seconds, $minutes, $hours, $monthday, $month,
328 $year, $weekday, $yearday, $sommertime) = localtime(time);
329 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
330 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
331 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
332 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
333 $month = $monthnames[$month];
334 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
335 $year+=1900;
336 my $name = $prg;
338 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
339 print LOG_HANDLE $log_msg;
340 if( $foreground ) {
341 print STDERR $log_msg;
342 }
343 }
344 close( LOG_HANDLE );
345 }
346 }
349 #=== FUNCTION ================================================================
350 # NAME: check_cmdline_param
351 # PARAMETERS: nothing
352 # RETURNS: nothing
353 # DESCRIPTION: validates commandline parameter
354 #===============================================================================
355 sub check_cmdline_param () {
356 my $err_config;
357 my $err_counter = 0;
358 if(not defined($cfg_file)) {
359 $cfg_file = "/etc/gosa-si/server.conf";
360 if(! -r $cfg_file) {
361 $err_config = "please specify a config file";
362 $err_counter += 1;
363 }
364 }
365 if( $err_counter > 0 ) {
366 &usage( "", 1 );
367 if( defined( $err_config)) { print STDERR "$err_config\n"}
368 print STDERR "\n";
369 exit( -1 );
370 }
371 }
374 #=== FUNCTION ================================================================
375 # NAME: check_pid
376 # PARAMETERS: nothing
377 # RETURNS: nothing
378 # DESCRIPTION: handels pid processing
379 #===============================================================================
380 sub check_pid {
381 $pid = -1;
382 # Check, if we are already running
383 if( open(LOCK_FILE, "<$pid_file") ) {
384 $pid = <LOCK_FILE>;
385 if( defined $pid ) {
386 chomp( $pid );
387 if( -f "/proc/$pid/stat" ) {
388 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
389 if( $stat ) {
390 daemon_log("ERROR: Already running",1);
391 close( LOCK_FILE );
392 exit -1;
393 }
394 }
395 }
396 close( LOCK_FILE );
397 unlink( $pid_file );
398 }
400 # create a syslog msg if it is not to possible to open PID file
401 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
402 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
403 if (open(LOCK_FILE, '<', $pid_file)
404 && ($pid = <LOCK_FILE>))
405 {
406 chomp($pid);
407 $msg .= "(PID $pid)\n";
408 } else {
409 $msg .= "(unable to read PID)\n";
410 }
411 if( ! ($foreground) ) {
412 openlog( $0, "cons,pid", "daemon" );
413 syslog( "warning", $msg );
414 closelog();
415 }
416 else {
417 print( STDERR " $msg " );
418 }
419 exit( -1 );
420 }
421 }
423 #=== FUNCTION ================================================================
424 # NAME: import_modules
425 # PARAMETERS: module_path - string - abs. path to the directory the modules
426 # are stored
427 # RETURNS: nothing
428 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
429 # state is on is imported by "require 'file';"
430 #===============================================================================
431 sub import_modules {
432 daemon_log(" ", 1);
434 if (not -e $modules_path) {
435 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
436 }
438 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
439 while (defined (my $file = readdir (DIR))) {
440 if (not $file =~ /(\S*?).pm$/) {
441 next;
442 }
443 my $mod_name = $1;
445 # ArpHandler switch
446 if( $file =~ /ArpHandler.pm/ ) {
447 if( $arp_enabled eq "false" ) { next; }
448 }
450 eval { require $file; };
451 if ($@) {
452 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
453 daemon_log("$@", 1);
454 exit;
455 } else {
456 my $info = eval($mod_name.'::get_module_info()');
457 # Only load module if get_module_info() returns a non-null object
458 if( $info ) {
459 my ($input_address, $input_key, $event_hash) = @{$info};
460 $known_modules->{$mod_name} = $info;
461 daemon_log("0 INFO: module $mod_name loaded", 5);
462 }
463 }
464 }
466 close (DIR);
467 }
469 #=== FUNCTION ================================================================
470 # NAME: password_check
471 # PARAMETERS: nothing
472 # RETURNS: nothing
473 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
474 # the same password
475 #===============================================================================
476 sub password_check {
477 my $passwd_hash = {};
478 while (my ($mod_name, $mod_info) = each %$known_modules) {
479 my $mod_passwd = @$mod_info[1];
480 if (not defined $mod_passwd) { next; }
481 if (not exists $passwd_hash->{$mod_passwd}) {
482 $passwd_hash->{$mod_passwd} = $mod_name;
484 # escalates critical error
485 } else {
486 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
487 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
488 exit( -1 );
489 }
490 }
492 }
495 #=== FUNCTION ================================================================
496 # NAME: sig_int_handler
497 # PARAMETERS: signal - string - signal arose from system
498 # RETURNS: nothing
499 # DESCRIPTION: handels tasks to be done befor signal becomes active
500 #===============================================================================
501 sub sig_int_handler {
502 my ($signal) = @_;
504 # if (defined($ldap_handle)) {
505 # $ldap_handle->disconnect;
506 # }
507 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
510 daemon_log("shutting down gosa-si-server", 1);
511 system("kill `ps -C gosa-si-server -o pid=`");
512 }
513 $SIG{INT} = \&sig_int_handler;
516 sub check_key_and_xml_validity {
517 my ($crypted_msg, $module_key, $session_id) = @_;
518 my $msg;
519 my $msg_hash;
520 my $error_string;
521 eval{
522 $msg = &decrypt_msg($crypted_msg, $module_key);
524 if ($msg =~ /<xml>/i){
525 $msg =~ s/\s+/ /g; # just for better daemon_log
526 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
527 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
529 ##############
530 # check header
531 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
532 my $header_l = $msg_hash->{'header'};
533 if( 1 > @{$header_l} ) { die 'empty header tag'; }
534 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
535 my $header = @{$header_l}[0];
536 if( 0 == length $header) { die 'empty string in header tag'; }
538 ##############
539 # check source
540 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
541 my $source_l = $msg_hash->{'source'};
542 if( 1 > @{$source_l} ) { die 'empty source tag'; }
543 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
544 my $source = @{$source_l}[0];
545 if( 0 == length $source) { die 'source error'; }
547 ##############
548 # check target
549 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
550 my $target_l = $msg_hash->{'target'};
551 if( 1 > @{$target_l} ) { die 'empty target tag'; }
552 }
553 };
554 if($@) {
555 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
556 $msg = undef;
557 $msg_hash = undef;
558 }
560 return ($msg, $msg_hash);
561 }
564 sub check_outgoing_xml_validity {
565 my ($msg, $session_id) = @_;
567 my $msg_hash;
568 eval{
569 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
571 ##############
572 # check header
573 my $header_l = $msg_hash->{'header'};
574 if( 1 != @{$header_l} ) {
575 die 'no or more than one headers specified';
576 }
577 my $header = @{$header_l}[0];
578 if( 0 == length $header) {
579 die 'header has length 0';
580 }
582 ##############
583 # check source
584 my $source_l = $msg_hash->{'source'};
585 if( 1 != @{$source_l} ) {
586 die 'no or more than 1 sources specified';
587 }
588 my $source = @{$source_l}[0];
589 if( 0 == length $source) {
590 die 'source has length 0';
591 }
592 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
593 $source =~ /^GOSA$/i ) {
594 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
595 }
597 ##############
598 # check target
599 my $target_l = $msg_hash->{'target'};
600 if( 0 == @{$target_l} ) {
601 die "no targets specified";
602 }
603 foreach my $target (@$target_l) {
604 if( 0 == length $target) {
605 die "target has length 0";
606 }
607 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
608 $target =~ /^GOSA$/i ||
609 $target =~ /^\*$/ ||
610 $target =~ /KNOWN_SERVER/i ||
611 $target =~ /JOBDB/i ||
612 $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 ){
613 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
614 }
615 }
616 };
617 if($@) {
618 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
619 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
620 $msg_hash = undef;
621 }
623 return ($msg_hash);
624 }
627 sub input_from_known_server {
628 my ($input, $remote_ip, $session_id) = @_ ;
629 my ($msg, $msg_hash, $module);
631 my $sql_statement= "SELECT * FROM known_server";
632 my $query_res = $known_server_db->select_dbentry( $sql_statement );
634 while( my ($hit_num, $hit) = each %{ $query_res } ) {
635 my $host_name = $hit->{hostname};
636 if( not $host_name =~ "^$remote_ip") {
637 next;
638 }
639 my $host_key = $hit->{hostkey};
640 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
641 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
643 # check if module can open msg envelope with module key
644 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
645 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
646 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
647 daemon_log("$@", 8);
648 next;
649 }
650 else {
651 $msg = $tmp_msg;
652 $msg_hash = $tmp_msg_hash;
653 $module = "ServerPackages";
654 last;
655 }
656 }
658 if( (!$msg) || (!$msg_hash) || (!$module) ) {
659 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
660 }
662 return ($msg, $msg_hash, $module);
663 }
666 sub input_from_known_client {
667 my ($input, $remote_ip, $session_id) = @_ ;
668 my ($msg, $msg_hash, $module);
670 my $sql_statement= "SELECT * FROM known_clients";
671 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
672 while( my ($hit_num, $hit) = each %{ $query_res } ) {
673 my $host_name = $hit->{hostname};
674 if( not $host_name =~ /^$remote_ip:\d*$/) {
675 next;
676 }
677 my $host_key = $hit->{hostkey};
678 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
679 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
681 # check if module can open msg envelope with module key
682 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
684 if( (!$msg) || (!$msg_hash) ) {
685 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
686 &daemon_log("$@", 8);
687 next;
688 }
689 else {
690 $module = "ClientPackages";
691 last;
692 }
693 }
695 if( (!$msg) || (!$msg_hash) || (!$module) ) {
696 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
697 }
699 return ($msg, $msg_hash, $module);
700 }
703 sub input_from_unknown_host {
704 no strict "refs";
705 my ($input, $session_id) = @_ ;
706 my ($msg, $msg_hash, $module);
707 my $error_string;
709 my %act_modules = %$known_modules;
711 while( my ($mod, $info) = each(%act_modules)) {
713 # check a key exists for this module
714 my $module_key = ${$mod."_key"};
715 if( not defined $module_key ) {
716 if( $mod eq 'ArpHandler' ) {
717 next;
718 }
719 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
720 next;
721 }
722 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
724 # check if module can open msg envelope with module key
725 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
726 if( (not defined $msg) || (not defined $msg_hash) ) {
727 next;
728 } else {
729 $module = $mod;
730 last;
731 }
732 }
734 if( (!$msg) || (!$msg_hash) || (!$module)) {
735 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
736 }
738 return ($msg, $msg_hash, $module);
739 }
742 sub create_ciphering {
743 my ($passwd) = @_;
744 if((!defined($passwd)) || length($passwd)==0) {
745 $passwd = "";
746 }
747 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
748 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
749 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
750 $my_cipher->set_iv($iv);
751 return $my_cipher;
752 }
755 sub encrypt_msg {
756 my ($msg, $key) = @_;
757 my $my_cipher = &create_ciphering($key);
758 my $len;
759 {
760 use bytes;
761 $len= 16-length($msg)%16;
762 }
763 $msg = "\0"x($len).$msg;
764 $msg = $my_cipher->encrypt($msg);
765 chomp($msg = &encode_base64($msg));
766 # there are no newlines allowed inside msg
767 $msg=~ s/\n//g;
768 return $msg;
769 }
772 sub decrypt_msg {
774 my ($msg, $key) = @_ ;
775 $msg = &decode_base64($msg);
776 my $my_cipher = &create_ciphering($key);
777 $msg = $my_cipher->decrypt($msg);
778 $msg =~ s/\0*//g;
779 return $msg;
780 }
783 sub get_encrypt_key {
784 my ($target) = @_ ;
785 my $encrypt_key;
786 my $error = 0;
788 # target can be in known_server
789 if( not defined $encrypt_key ) {
790 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
791 my $query_res = $known_server_db->select_dbentry( $sql_statement );
792 while( my ($hit_num, $hit) = each %{ $query_res } ) {
793 my $host_name = $hit->{hostname};
794 if( $host_name ne $target ) {
795 next;
796 }
797 $encrypt_key = $hit->{hostkey};
798 last;
799 }
800 }
802 # target can be in known_client
803 if( not defined $encrypt_key ) {
804 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
805 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
806 while( my ($hit_num, $hit) = each %{ $query_res } ) {
807 my $host_name = $hit->{hostname};
808 if( $host_name ne $target ) {
809 next;
810 }
811 $encrypt_key = $hit->{hostkey};
812 last;
813 }
814 }
816 return $encrypt_key;
817 }
820 #=== FUNCTION ================================================================
821 # NAME: open_socket
822 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
823 # [PeerPort] string necessary if port not appended by PeerAddr
824 # RETURNS: socket IO::Socket::INET
825 # DESCRIPTION: open a socket to PeerAddr
826 #===============================================================================
827 sub open_socket {
828 my ($PeerAddr, $PeerPort) = @_ ;
829 if(defined($PeerPort)){
830 $PeerAddr = $PeerAddr.":".$PeerPort;
831 }
832 my $socket;
833 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
834 Porto => "tcp",
835 Type => SOCK_STREAM,
836 Timeout => 5,
837 );
838 if(not defined $socket) {
839 return;
840 }
841 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
842 return $socket;
843 }
846 #sub get_local_ip_for_remote_ip {
847 # my $remote_ip= shift;
848 # my $result="0.0.0.0";
849 #
850 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
851 # if($remote_ip eq "127.0.0.1") {
852 # $result = "127.0.0.1";
853 # } else {
854 # my $PROC_NET_ROUTE= ('/proc/net/route');
855 #
856 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
857 # or die "Could not open $PROC_NET_ROUTE";
858 #
859 # my @ifs = <PROC_NET_ROUTE>;
860 #
861 # close(PROC_NET_ROUTE);
862 #
863 # # Eat header line
864 # shift @ifs;
865 # chomp @ifs;
866 # foreach my $line(@ifs) {
867 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
868 # my $destination;
869 # my $mask;
870 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
871 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
872 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
873 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
875 # # destination matches route, save mac and exit
876 # $result= &get_ip($Iface);
877 # last;
878 # }
879 # }
880 # }
881 # } else {
882 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
883 # }
884 # return $result;
885 #}
888 sub send_msg_to_target {
889 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
890 my $error = 0;
891 my $header;
892 my $timestamp = &get_time();
893 my $new_status;
894 my $act_status;
895 my ($sql_statement, $res);
897 if( $msg_header ) {
898 $header = "'$msg_header'-";
899 } else {
900 $header = "";
901 }
903 # Patch the source ip
904 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
905 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
906 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
907 }
909 # encrypt xml msg
910 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
912 # opensocket
913 my $socket = &open_socket($address);
914 if( !$socket ) {
915 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
916 $error++;
917 }
919 if( $error == 0 ) {
920 # send xml msg
921 print $socket $crypted_msg."\n";
923 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
924 daemon_log("$session_id DEBUG: message:\n$msg", 9);
926 }
928 # close socket in any case
929 if( $socket ) {
930 close $socket;
931 }
933 if( $error > 0 ) { $new_status = "down"; }
934 else { $new_status = $msg_header; }
937 # known_clients
938 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
939 $res = $known_clients_db->select_dbentry($sql_statement);
940 if( keys(%$res) == 1) {
941 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
942 if ($act_status eq "down" && $new_status eq "down") {
943 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
944 $res = $known_clients_db->del_dbentry($sql_statement);
945 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
946 } else {
947 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
948 $res = $known_clients_db->update_dbentry($sql_statement);
949 if($new_status eq "down"){
950 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
951 } else {
952 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
953 }
954 }
955 }
957 # known_server
958 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
959 $res = $known_server_db->select_dbentry($sql_statement);
960 if( keys(%$res) == 1) {
961 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
962 if ($act_status eq "down" && $new_status eq "down") {
963 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
964 $res = $known_server_db->del_dbentry($sql_statement);
965 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
966 }
967 else {
968 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
969 $res = $known_server_db->update_dbentry($sql_statement);
970 if($new_status eq "down"){
971 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
972 } else {
973 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
974 }
975 }
976 }
977 return $error;
978 }
981 sub update_jobdb_status_for_send_msgs {
982 my ($session_id, $answer, $error) = @_;
983 &daemon_log("$session_id DEBUG: try to update job status", 7);
984 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
985 my $jobdb_id = $1;
986 $answer =~ /<header>(.*)<\/header>/;
987 my $job_header = $1;
988 $answer =~ /<target>(.*)<\/target>/;
989 my $job_target = $1;
991 # sending msg failed
992 if( $error ) {
994 # set jobs to done, jobs do not need to deliver their message in any case
995 if (($job_header eq "trigger_action_localboot")
996 ||($job_header eq "trigger_action_lock")
997 ||($job_header eq "trigger_action_halt")
998 ) {
999 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1000 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1001 my $res = $job_db->update_dbentry($sql_statement);
1003 # reactivate jobs, jobs need to deliver their message
1004 } elsif (($job_header eq "trigger_action_activate")
1005 ||($job_header eq "trigger_action_update")
1006 ||($job_header eq "trigger_action_reinstall")
1007 ||($job_header eq "trigger_activate_new")
1008 ) {
1009 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1011 # for all other messages
1012 } else {
1013 my $sql_statement = "UPDATE $job_queue_tn ".
1014 "SET status='error', result='can not deliver msg, please consult log file' ".
1015 "WHERE id=$jobdb_id";
1016 my $res = $job_db->update_dbentry($sql_statement);
1017 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1018 }
1020 # sending msg was successful
1021 } else {
1022 # set jobs localboot, lock, activate, halt, reboot and wake to done
1023 # jobs reinstall, update, inst_update do themself setting to done
1024 if (($job_header eq "trigger_action_localboot")
1025 ||($job_header eq "trigger_action_lock")
1026 ||($job_header eq "trigger_action_activate")
1027 ||($job_header eq "trigger_action_halt")
1028 ||($job_header eq "trigger_action_reboot")
1029 ||($job_header eq "trigger_action_wake")
1030 ||($job_header eq "trigger_wake")
1031 ) {
1033 my $sql_statement = "UPDATE $job_queue_tn ".
1034 "SET status='done' ".
1035 "WHERE id=$jobdb_id AND status='processed'";
1036 my $res = $job_db->update_dbentry($sql_statement);
1037 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1038 } else {
1039 &daemon_log("$session_id WARNING: sending message succeed but cannot update job status.", 3);
1040 }
1041 }
1042 } else {
1043 &daemon_log("$session_id ERROR: cannot update job status, msg has no jobdb_id-tag: $answer", 1);
1044 }
1045 }
1047 sub reactivate_job_with_delay {
1048 my ($session_id, $target, $header, $delay) = @_ ;
1049 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1051 if (not defined $delay) { $delay = 30 } ;
1052 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1054 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1055 my $res = $job_db->update_dbentry($sql);
1056 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1057 "cause client '$target' is currently not available", 5);
1058 daemon_log("$session_id $sql", 7);
1059 return;
1060 }
1063 sub sig_handler {
1064 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1065 daemon_log("0 INFO got signal '$signal'", 1);
1066 $kernel->sig_handled();
1067 return;
1068 }
1071 sub msg_to_decrypt {
1072 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1073 my $session_id = $session->ID;
1074 my ($msg, $msg_hash, $module);
1075 my $error = 0;
1077 # fetch new msg out of @msgs_to_decrypt
1078 my $tmp_next_msg = shift @msgs_to_decrypt;
1079 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1081 # msg is from a new client or gosa
1082 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1083 # msg is from a gosa-si-server
1084 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1085 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1086 }
1087 # msg is from a gosa-si-client
1088 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1089 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1090 }
1091 # an error occurred
1092 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1093 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1094 # could not understand a msg from its server the client cause a re-registering process
1095 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1096 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1098 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1099 "' to cause a re-registering of the client if necessary", 3);
1100 $error++;
1101 }
1103 my $header;
1104 my $target;
1105 my $source;
1106 my $done = 0;
1107 my $sql;
1108 my $res;
1110 # check whether this message should be processed here
1111 if ($error == 0) {
1112 $header = @{$msg_hash->{'header'}}[0];
1113 $target = @{$msg_hash->{'target'}}[0];
1114 $source = @{$msg_hash->{'source'}}[0];
1115 my $not_found_in_known_clients_db = 0;
1116 my $not_found_in_known_server_db = 0;
1117 my $not_found_in_foreign_clients_db = 0;
1118 my $local_address;
1119 my $local_mac;
1120 my ($target_ip, $target_port) = split(':', $target);
1122 # Determine the local ip address if target is an ip address
1123 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1124 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1125 } else {
1126 $local_address = $server_address;
1127 }
1129 # Determine the local mac address if target is a mac address
1130 if ($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) {
1131 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1132 my $network_interface= &get_interface_for_ip($loc_ip);
1133 $local_mac = &get_mac_for_interface($network_interface);
1134 } else {
1135 $local_mac = $server_mac_address;
1136 }
1138 # target and source is equal to GOSA -> process here
1139 if (not $done) {
1140 if ($target eq "GOSA" && $source eq "GOSA") {
1141 $done = 1;
1142 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1143 }
1144 }
1146 # target is own address without forward_to_gosa-tag -> process here
1147 if (not $done) {
1148 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1149 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1150 $done = 1;
1151 if ($source eq "GOSA") {
1152 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1153 }
1154 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1155 }
1156 }
1158 # target is a client address in known_clients -> process here
1159 if (not $done) {
1160 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1161 $res = $known_clients_db->select_dbentry($sql);
1162 if (keys(%$res) > 0) {
1163 $done = 1;
1164 my $hostname = $res->{1}->{'hostname'};
1165 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1166 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1167 if ($source eq "GOSA") {
1168 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1169 }
1170 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1172 } else {
1173 $not_found_in_known_clients_db = 1;
1174 }
1175 }
1177 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1178 if (not $done) {
1179 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1180 my $gosa_at;
1181 my $gosa_session_id;
1182 if (($target eq $local_address) && (defined $forward_to_gosa)){
1183 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1184 if ($gosa_at ne $local_address) {
1185 $done = 1;
1186 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1187 }
1188 }
1189 }
1191 # if message should be processed here -> add message to incoming_db
1192 if ($done) {
1193 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1194 # so gosa-si-server knows how to process this kind of messages
1195 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1196 $module = "GosaPackages";
1197 }
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 }
1210 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1211 if (not $done) {
1212 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1213 my $gosa_at;
1214 my $gosa_session_id;
1215 if (($target eq $local_address) && (defined $forward_to_gosa)){
1216 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1217 if ($gosa_at eq $local_address) {
1218 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1219 if( defined $session_reference ) {
1220 $heap = $session_reference->get_heap();
1221 }
1222 if(exists $heap->{'client'}) {
1223 $msg = &encrypt_msg($msg, $GosaPackages_key);
1224 $heap->{'client'}->put($msg);
1225 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1226 }
1227 $done = 1;
1228 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1229 }
1230 }
1232 }
1234 # target is a client address in foreign_clients -> forward to registration server
1235 if (not $done) {
1236 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1237 $res = $foreign_clients_db->select_dbentry($sql);
1238 if (keys(%$res) > 0) {
1239 my $hostname = $res->{1}->{'hostname'};
1240 my ($host_ip, $host_port) = split(/:/, $hostname);
1241 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1242 my $regserver = $res->{1}->{'regserver'};
1243 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1244 my $res = $known_server_db->select_dbentry($sql);
1245 if (keys(%$res) > 0) {
1246 my $regserver_key = $res->{1}->{'hostkey'};
1247 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1248 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1249 if ($source eq "GOSA") {
1250 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1251 }
1252 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1253 }
1254 $done = 1;
1255 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1256 } else {
1257 $not_found_in_foreign_clients_db = 1;
1258 }
1259 }
1261 # target is a server address -> forward to server
1262 if (not $done) {
1263 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1264 $res = $known_server_db->select_dbentry($sql);
1265 if (keys(%$res) > 0) {
1266 my $hostkey = $res->{1}->{'hostkey'};
1268 if ($source eq "GOSA") {
1269 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1270 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1272 }
1274 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1275 $done = 1;
1276 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1277 } else {
1278 $not_found_in_known_server_db = 1;
1279 }
1280 }
1283 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1284 if ( $not_found_in_foreign_clients_db
1285 && $not_found_in_known_server_db
1286 && $not_found_in_known_clients_db) {
1287 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1288 $module = "GosaPackages";
1289 }
1292 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1293 primkey=>[],
1294 headertag=>$header,
1295 targettag=>$target,
1296 xmlmessage=>&encode_base64($msg),
1297 timestamp=>&get_time,
1298 module=>$module,
1299 sessionid=>$session_id,
1300 } );
1301 $done = 1;
1302 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1303 }
1306 if (not $done) {
1307 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1308 if ($source eq "GOSA") {
1309 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1310 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1312 my $session_reference = $kernel->ID_id_to_session($session_id);
1313 if( defined $session_reference ) {
1314 $heap = $session_reference->get_heap();
1315 }
1316 if(exists $heap->{'client'}) {
1317 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1318 $heap->{'client'}->put($error_msg);
1319 }
1320 }
1321 }
1323 }
1325 return;
1326 }
1329 sub next_task {
1330 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1331 my $running_task = POE::Wheel::Run->new(
1332 Program => sub { process_task($session, $heap, $task) },
1333 StdioFilter => POE::Filter::Reference->new(),
1334 StdoutEvent => "task_result",
1335 StderrEvent => "task_debug",
1336 CloseEvent => "task_done",
1337 );
1338 $heap->{task}->{ $running_task->ID } = $running_task;
1339 }
1341 sub handle_task_result {
1342 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1343 my $client_answer = $result->{'answer'};
1344 if( $client_answer =~ s/session_id=(\d+)$// ) {
1345 my $session_id = $1;
1346 if( defined $session_id ) {
1347 my $session_reference = $kernel->ID_id_to_session($session_id);
1348 if( defined $session_reference ) {
1349 $heap = $session_reference->get_heap();
1350 }
1351 }
1353 if(exists $heap->{'client'}) {
1354 $heap->{'client'}->put($client_answer);
1355 }
1356 }
1357 $kernel->sig(CHLD => "child_reap");
1358 }
1360 sub handle_task_debug {
1361 my $result = $_[ARG0];
1362 print STDERR "$result\n";
1363 }
1365 sub handle_task_done {
1366 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1367 delete $heap->{task}->{$task_id};
1368 }
1370 sub process_task {
1371 no strict "refs";
1372 #CHECK: Not @_[...]?
1373 my ($session, $heap, $task) = @_;
1374 my $error = 0;
1375 my $answer_l;
1376 my ($answer_header, @answer_target_l, $answer_source);
1377 my $client_answer = "";
1379 # prepare all variables needed to process message
1380 #my $msg = $task->{'xmlmessage'};
1381 my $msg = &decode_base64($task->{'xmlmessage'});
1382 my $incoming_id = $task->{'id'};
1383 my $module = $task->{'module'};
1384 my $header = $task->{'headertag'};
1385 my $session_id = $task->{'sessionid'};
1386 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1387 my $source = @{$msg_hash->{'source'}}[0];
1389 # set timestamp of incoming client uptodate, so client will not
1390 # be deleted from known_clients because of expiration
1391 my $act_time = &get_time();
1392 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1393 my $res = $known_clients_db->exec_statement($sql);
1395 ######################
1396 # process incoming msg
1397 if( $error == 0) {
1398 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1399 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1400 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1402 if ( 0 < @{$answer_l} ) {
1403 my $answer_str = join("\n", @{$answer_l});
1404 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1405 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1406 }
1407 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1408 } else {
1409 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1410 }
1412 }
1413 if( !$answer_l ) { $error++ };
1415 ########
1416 # answer
1417 if( $error == 0 ) {
1419 foreach my $answer ( @{$answer_l} ) {
1420 # check outgoing msg to xml validity
1421 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1422 if( not defined $answer_hash ) { next; }
1424 $answer_header = @{$answer_hash->{'header'}}[0];
1425 @answer_target_l = @{$answer_hash->{'target'}};
1426 $answer_source = @{$answer_hash->{'source'}}[0];
1428 # deliver msg to all targets
1429 foreach my $answer_target ( @answer_target_l ) {
1431 # targets of msg are all gosa-si-clients in known_clients_db
1432 if( $answer_target eq "*" ) {
1433 # answer is for all clients
1434 my $sql_statement= "SELECT * FROM known_clients";
1435 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1436 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1437 my $host_name = $hit->{hostname};
1438 my $host_key = $hit->{hostkey};
1439 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1440 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1441 }
1442 }
1444 # targets of msg are all gosa-si-server in known_server_db
1445 elsif( $answer_target eq "KNOWN_SERVER" ) {
1446 # answer is for all server in known_server
1447 my $sql_statement= "SELECT * FROM $known_server_tn";
1448 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1449 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1450 my $host_name = $hit->{hostname};
1451 my $host_key = $hit->{hostkey};
1452 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1453 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1454 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1455 }
1456 }
1458 # target of msg is GOsa
1459 elsif( $answer_target eq "GOSA" ) {
1460 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1461 my $add_on = "";
1462 if( defined $session_id ) {
1463 $add_on = ".session_id=$session_id";
1464 }
1465 # answer is for GOSA and has to returned to connected client
1466 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1467 $client_answer = $gosa_answer.$add_on;
1468 }
1470 # target of msg is job queue at this host
1471 elsif( $answer_target eq "JOBDB") {
1472 $answer =~ /<header>(\S+)<\/header>/;
1473 my $header;
1474 if( defined $1 ) { $header = $1; }
1475 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1476 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1477 }
1479 # Target of msg is a mac address
1480 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 ) {
1481 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1482 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1483 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1484 my $found_ip_flag = 0;
1485 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1486 my $host_name = $hit->{hostname};
1487 my $host_key = $hit->{hostkey};
1488 $answer =~ s/$answer_target/$host_name/g;
1489 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1490 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1491 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1492 $found_ip_flag++ ;
1493 }
1494 if ($found_ip_flag == 0) {
1495 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1496 my $res = $foreign_clients_db->select_dbentry($sql);
1497 while( my ($hit_num, $hit) = each %{ $res } ) {
1498 my $host_name = $hit->{hostname};
1499 my $reg_server = $hit->{regserver};
1500 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1502 # Fetch key for reg_server
1503 my $reg_server_key;
1504 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1505 my $res = $known_server_db->select_dbentry($sql);
1506 if (exists $res->{1}) {
1507 $reg_server_key = $res->{1}->{'hostkey'};
1508 } else {
1509 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1510 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1511 $reg_server_key = undef;
1512 }
1514 # Send answer to server where client is registered
1515 if (defined $reg_server_key) {
1516 $answer =~ s/$answer_target/$host_name/g;
1517 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1518 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1519 $found_ip_flag++ ;
1520 }
1521 }
1522 }
1523 if( $found_ip_flag == 0) {
1524 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1525 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1526 }
1528 # Answer is for one specific host
1529 } else {
1530 # get encrypt_key
1531 my $encrypt_key = &get_encrypt_key($answer_target);
1532 if( not defined $encrypt_key ) {
1533 # unknown target
1534 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1535 next;
1536 }
1537 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1538 &update_jobdb_status_for_send_msgs($session_id,$answer, $error);
1539 }
1540 }
1541 }
1542 }
1544 my $filter = POE::Filter::Reference->new();
1545 my %result = (
1546 status => "seems ok to me",
1547 answer => $client_answer,
1548 );
1550 my $output = $filter->put( [ \%result ] );
1551 print @$output;
1554 }
1556 sub session_start {
1557 my ($kernel) = $_[KERNEL];
1558 $global_kernel = $kernel;
1559 $kernel->yield('register_at_foreign_servers');
1560 $kernel->yield('create_fai_server_db', $fai_server_tn );
1561 $kernel->yield('create_fai_release_db', $fai_release_tn );
1562 $kernel->yield('watch_for_next_tasks');
1563 $kernel->sig(USR1 => "sig_handler");
1564 $kernel->sig(USR2 => "recreate_packages_db");
1565 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1566 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1567 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1568 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1569 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1570 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1571 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1573 # Start opsi check
1574 if ($opsi_enabled eq "true") {
1575 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1576 }
1578 }
1581 sub watch_for_done_jobs {
1582 #CHECK: $heap for what?
1583 my ($kernel,$heap) = @_[KERNEL, HEAP];
1585 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1586 my $res = $job_db->select_dbentry( $sql_statement );
1588 while( my ($id, $hit) = each %{$res} ) {
1589 my $jobdb_id = $hit->{id};
1590 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1591 my $res = $job_db->del_dbentry($sql_statement);
1592 }
1594 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1595 }
1598 sub watch_for_opsi_jobs {
1599 my ($kernel) = $_[KERNEL];
1601 # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a
1602 # opsi install job is to parse the xml message. There is still the correct header.
1603 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1604 my $res = $job_db->select_dbentry( $sql_statement );
1606 # Ask OPSI for an update of the running jobs
1607 while (my ($id, $hit) = each %$res ) {
1608 # Determine current parameters of the job
1609 my $hostId = $hit->{'plainname'};
1610 my $macaddress = $hit->{'macaddress'};
1611 my $progress = $hit->{'progress'};
1613 my $result= {};
1615 # For hosts, only return the products that are or get installed
1616 my $callobj;
1617 $callobj = {
1618 method => 'getProductStates_hash',
1619 params => [ $hostId ],
1620 id => 1,
1621 };
1623 my $hres = $opsi_client->call($opsi_url, $callobj);
1624 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1625 if (not &check_opsi_res($hres)) {
1626 my $htmp= $hres->result->{$hostId};
1628 # Check state != not_installed or action == setup -> load and add
1629 my $products= 0;
1630 my $installed= 0;
1631 my $installing = 0;
1632 my $error= 0;
1633 my @installed_list;
1634 my @error_list;
1635 my $act_status = "none";
1636 foreach my $product (@{$htmp}){
1638 if ($product->{'installationStatus'} ne "not_installed" or
1639 $product->{'actionRequest'} eq "setup"){
1641 # Increase number of products for this host
1642 $products++;
1644 if ($product->{'installationStatus'} eq "failed"){
1645 $result->{$product->{'productId'}}= "error";
1646 unshift(@error_list, $product->{'productId'});
1647 $error++;
1648 }
1649 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1650 $result->{$product->{'productId'}}= "installed";
1651 unshift(@installed_list, $product->{'productId'});
1652 $installed++;
1653 }
1654 if ($product->{'installationStatus'} eq "installing"){
1655 $result->{$product->{'productId'}}= "installing";
1656 $installing++;
1657 $act_status = "installing - ".$product->{'productId'};
1658 }
1659 }
1660 }
1662 # Estimate "rough" progress, avoid division by zero
1663 if ($products == 0) {
1664 $result->{'progress'}= 0;
1665 } else {
1666 $result->{'progress'}= int($installed * 100 / $products);
1667 }
1669 # Set updates in job queue
1670 if ((not $error) && (not $installing) && ($installed)) {
1671 $act_status = "installed - ".join(", ", @installed_list);
1672 }
1673 if ($error) {
1674 $act_status = "error - ".join(", ", @error_list);
1675 }
1676 if ($progress ne $result->{'progress'} ) {
1677 # Updating progress and result
1678 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1679 my $update_res = $job_db->update_dbentry($update_statement);
1680 }
1681 if ($progress eq 100) {
1682 # Updateing status
1683 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1684 if ($error) {
1685 $done_statement .= "status='error'";
1686 } else {
1687 $done_statement .= "status='done'";
1688 }
1689 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1690 my $done_res = $job_db->update_dbentry($done_statement);
1691 }
1694 }
1695 }
1697 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1698 }
1701 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1702 sub watch_for_modified_jobs {
1703 my ($kernel,$heap) = @_[KERNEL, HEAP];
1705 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1706 my $res = $job_db->select_dbentry( $sql_statement );
1708 # if db contains no jobs which should be update, do nothing
1709 if (keys %$res != 0) {
1711 if ($job_synchronization eq "true") {
1712 # make out of the db result a gosa-si message
1713 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1715 # update all other SI-server
1716 &inform_all_other_si_server($update_msg);
1717 }
1719 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1720 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1721 $res = $job_db->update_dbentry($sql_statement);
1722 }
1724 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1725 }
1728 sub watch_for_new_jobs {
1729 if($watch_for_new_jobs_in_progress == 0) {
1730 $watch_for_new_jobs_in_progress = 1;
1731 my ($kernel,$heap) = @_[KERNEL, HEAP];
1733 # check gosa job quaeue for jobs with executable timestamp
1734 my $timestamp = &get_time();
1735 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1736 my $res = $job_db->exec_statement( $sql_statement );
1738 # Merge all new jobs that would do the same actions
1739 my @drops;
1740 my $hits;
1741 foreach my $hit (reverse @{$res} ) {
1742 my $macaddress= lc @{$hit}[8];
1743 my $headertag= @{$hit}[5];
1744 if(
1745 defined($hits->{$macaddress}) &&
1746 defined($hits->{$macaddress}->{$headertag}) &&
1747 defined($hits->{$macaddress}->{$headertag}[0])
1748 ) {
1749 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1750 }
1751 $hits->{$macaddress}->{$headertag}= $hit;
1752 }
1754 # Delete new jobs with a matching job in state 'processing'
1755 foreach my $macaddress (keys %{$hits}) {
1756 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1757 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1758 if(defined($jobdb_id)) {
1759 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1760 my $res = $job_db->exec_statement( $sql_statement );
1761 foreach my $hit (@{$res}) {
1762 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1763 }
1764 } else {
1765 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1766 }
1767 }
1768 }
1770 # Commit deletion
1771 $job_db->exec_statementlist(\@drops);
1773 # Look for new jobs that could be executed
1774 foreach my $macaddress (keys %{$hits}) {
1776 # Look if there is an executing job
1777 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1778 my $res = $job_db->exec_statement( $sql_statement );
1780 # Skip new jobs for host if there is a processing job
1781 if(defined($res) and defined @{$res}[0]) {
1782 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1783 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1784 if(@{$row}[5] eq 'trigger_action_reinstall') {
1785 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1786 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1787 if(defined($res_2) and defined @{$res_2}[0]) {
1788 # Set status from goto-activation to 'waiting' and update timestamp
1789 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1790 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1791 }
1792 }
1793 next;
1794 }
1796 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1797 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1798 if(defined($jobdb_id)) {
1799 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1801 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1802 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1803 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1805 # expect macaddress is unique!!!!!!
1806 my $target = $res_hash->{1}->{hostname};
1808 # change header
1809 $job_msg =~ s/<header>job_/<header>gosa_/;
1811 # add sqlite_id
1812 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1814 $job_msg =~ /<header>(\S+)<\/header>/;
1815 my $header = $1 ;
1816 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1818 # update status in job queue to ...
1819 # ... 'processing', for jobs: 'reinstall', 'update'
1820 if (($header =~ /gosa_trigger_action_reinstall/)
1821 || ($header =~ /gosa_trigger_activate_new/)
1822 || ($header =~ /gosa_trigger_action_update/)) {
1823 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1824 my $dbres = $job_db->update_dbentry($sql_statement);
1825 }
1827 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1828 else {
1829 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1830 my $dbres = $job_db->update_dbentry($sql_statement);
1831 }
1833 # We don't want parallel processing
1834 last;
1835 }
1836 }
1837 }
1839 $watch_for_new_jobs_in_progress = 0;
1840 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1841 }
1842 }
1845 sub watch_for_new_messages {
1846 my ($kernel,$heap) = @_[KERNEL, HEAP];
1847 my @coll_user_msg; # collection list of outgoing messages
1849 # check messaging_db for new incoming messages with executable timestamp
1850 my $timestamp = &get_time();
1851 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1852 my $res = $messaging_db->exec_statement( $sql_statement );
1853 foreach my $hit (@{$res}) {
1855 # create outgoing messages
1856 my $message_to = @{$hit}[3];
1857 # translate message_to to plain login name
1858 my @message_to_l = split(/,/, $message_to);
1859 my %receiver_h;
1860 foreach my $receiver (@message_to_l) {
1861 if ($receiver =~ /^u_([\s\S]*)$/) {
1862 $receiver_h{$1} = 0;
1863 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1864 my $group_name = $1;
1865 # fetch all group members from ldap and add them to receiver hash
1866 my $ldap_handle = &get_ldap_handle();
1867 if (defined $ldap_handle) {
1868 my $mesg = $ldap_handle->search(
1869 base => $ldap_base,
1870 scope => 'sub',
1871 attrs => ['memberUid'],
1872 filter => "cn=$group_name",
1873 );
1874 if ($mesg->count) {
1875 my @entries = $mesg->entries;
1876 foreach my $entry (@entries) {
1877 my @receivers= $entry->get_value("memberUid");
1878 foreach my $receiver (@receivers) {
1879 $receiver_h{$receiver} = 0;
1880 }
1881 }
1882 }
1883 # translating errors ?
1884 if ($mesg->code) {
1885 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1886 }
1887 # ldap handle error ?
1888 } else {
1889 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1890 }
1891 } else {
1892 my $sbjct = &encode_base64(@{$hit}[1]);
1893 my $msg = &encode_base64(@{$hit}[7]);
1894 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1895 }
1896 }
1897 my @receiver_l = keys(%receiver_h);
1899 my $message_id = @{$hit}[0];
1901 #add each outgoing msg to messaging_db
1902 my $receiver;
1903 foreach $receiver (@receiver_l) {
1904 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1905 "VALUES ('".
1906 $message_id."', '". # id
1907 @{$hit}[1]."', '". # subject
1908 @{$hit}[2]."', '". # message_from
1909 $receiver."', '". # message_to
1910 "none"."', '". # flag
1911 "out"."', '". # direction
1912 @{$hit}[6]."', '". # delivery_time
1913 @{$hit}[7]."', '". # message
1914 $timestamp."'". # timestamp
1915 ")";
1916 &daemon_log("M DEBUG: $sql_statement", 1);
1917 my $res = $messaging_db->exec_statement($sql_statement);
1918 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1919 }
1921 # set incoming message to flag d=deliverd
1922 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1923 &daemon_log("M DEBUG: $sql_statement", 7);
1924 $res = $messaging_db->update_dbentry($sql_statement);
1925 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1926 }
1928 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1929 return;
1930 }
1932 sub watch_for_delivery_messages {
1933 my ($kernel, $heap) = @_[KERNEL, HEAP];
1935 # select outgoing messages
1936 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1937 #&daemon_log("0 DEBUG: $sql", 7);
1938 my $res = $messaging_db->exec_statement( $sql_statement );
1940 # build out msg for each usr
1941 foreach my $hit (@{$res}) {
1942 my $receiver = @{$hit}[3];
1943 my $msg_id = @{$hit}[0];
1944 my $subject = @{$hit}[1];
1945 my $message = @{$hit}[7];
1947 # resolve usr -> host where usr is logged in
1948 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1949 #&daemon_log("0 DEBUG: $sql", 7);
1950 my $res = $login_users_db->exec_statement($sql);
1952 # reciver is logged in nowhere
1953 if (not ref(@$res[0]) eq "ARRAY") { next; }
1955 my $send_succeed = 0;
1956 foreach my $hit (@$res) {
1957 my $receiver_host = @$hit[0];
1958 my $delivered2host = 0;
1959 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1961 # Looking for host in know_clients_db
1962 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1963 my $res = $known_clients_db->exec_statement($sql);
1965 # Host is known in known_clients_db
1966 if (ref(@$res[0]) eq "ARRAY") {
1967 my $receiver_key = @{@{$res}[0]}[2];
1968 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1969 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1970 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1971 if ($error == 0 ) {
1972 $send_succeed++ ;
1973 $delivered2host++ ;
1974 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1975 } else {
1976 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1977 }
1978 }
1980 # Message already send, do not need to do anything more, otherwise ...
1981 if ($delivered2host) { next;}
1983 # ...looking for host in foreign_clients_db
1984 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1985 $res = $foreign_clients_db->exec_statement($sql);
1987 # Host is known in foreign_clients_db
1988 if (ref(@$res[0]) eq "ARRAY") {
1989 my $registration_server = @{@{$res}[0]}[2];
1991 # Fetch encryption key for registration server
1992 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1993 my $res = $known_server_db->exec_statement($sql);
1994 if (ref(@$res[0]) eq "ARRAY") {
1995 my $registration_server_key = @{@{$res}[0]}[3];
1996 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1997 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1998 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1999 if ($error == 0 ) {
2000 $send_succeed++ ;
2001 $delivered2host++ ;
2002 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2003 } else {
2004 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2005 }
2007 } else {
2008 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2009 "registrated at server '$registration_server', ".
2010 "but no data available in known_server_db ", 1);
2011 }
2012 }
2014 if (not $delivered2host) {
2015 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2016 }
2017 }
2019 if ($send_succeed) {
2020 # set outgoing msg at db to deliverd
2021 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2022 my $res = $messaging_db->exec_statement($sql);
2023 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2024 } else {
2025 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2026 }
2027 }
2029 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2030 return;
2031 }
2034 sub watch_for_done_messages {
2035 my ($kernel,$heap) = @_[KERNEL, HEAP];
2037 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2038 #&daemon_log("0 DEBUG: $sql", 7);
2039 my $res = $messaging_db->exec_statement($sql);
2041 foreach my $hit (@{$res}) {
2042 my $msg_id = @{$hit}[0];
2044 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2045 #&daemon_log("0 DEBUG: $sql", 7);
2046 my $res = $messaging_db->exec_statement($sql);
2048 # not all usr msgs have been seen till now
2049 if ( ref(@$res[0]) eq "ARRAY") { next; }
2051 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2052 #&daemon_log("0 DEBUG: $sql", 7);
2053 $res = $messaging_db->exec_statement($sql);
2055 }
2057 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2058 return;
2059 }
2062 sub watch_for_old_known_clients {
2063 my ($kernel,$heap) = @_[KERNEL, HEAP];
2065 my $sql_statement = "SELECT * FROM $known_clients_tn";
2066 my $res = $known_clients_db->select_dbentry( $sql_statement );
2068 my $act_time = int(&get_time());
2070 while ( my ($hit_num, $hit) = each %$res) {
2071 my $expired_timestamp = int($hit->{'timestamp'});
2072 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2073 my $dt = DateTime->new( year => $1,
2074 month => $2,
2075 day => $3,
2076 hour => $4,
2077 minute => $5,
2078 second => $6,
2079 );
2081 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2082 $expired_timestamp = $dt->ymd('').$dt->hms('');
2083 if ($act_time > $expired_timestamp) {
2084 my $hostname = $hit->{'hostname'};
2085 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2086 my $del_res = $known_clients_db->exec_statement($del_sql);
2088 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2089 }
2091 }
2093 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2094 }
2097 sub watch_for_next_tasks {
2098 my ($kernel,$heap) = @_[KERNEL, HEAP];
2100 my $sql = "SELECT * FROM $incoming_tn";
2101 my $res = $incoming_db->select_dbentry($sql);
2103 while ( my ($hit_num, $hit) = each %$res) {
2104 my $headertag = $hit->{'headertag'};
2105 if ($headertag =~ /^answer_(\d+)/) {
2106 # do not start processing, this message is for a still running POE::Wheel
2107 next;
2108 }
2109 my $message_id = $hit->{'id'};
2110 $kernel->yield('next_task', $hit);
2112 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2113 my $res = $incoming_db->exec_statement($sql);
2114 }
2116 $kernel->delay_set('watch_for_next_tasks', 0.1);
2117 }
2120 sub get_ldap_handle {
2121 my ($session_id) = @_;
2122 my $heap;
2123 my $ldap_handle;
2125 if (not defined $session_id ) { $session_id = 0 };
2126 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2128 if ($session_id == 0) {
2129 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2130 $ldap_handle = Net::LDAP->new( $ldap_uri );
2131 if (defined $ldap_handle) {
2132 $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!");
2133 } else {
2134 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2135 }
2137 } else {
2138 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2139 if( defined $session_reference ) {
2140 $heap = $session_reference->get_heap();
2141 }
2143 if (not defined $heap) {
2144 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2145 return;
2146 }
2148 # TODO: This "if" is nonsense, because it doesn't prove that the
2149 # used handle is still valid - or if we've to reconnect...
2150 #if (not exists $heap->{ldap_handle}) {
2151 $ldap_handle = Net::LDAP->new( $ldap_uri );
2152 $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!");
2153 $heap->{ldap_handle} = $ldap_handle;
2154 #}
2155 }
2156 return $ldap_handle;
2157 }
2160 sub change_fai_state {
2161 my ($st, $targets, $session_id) = @_;
2162 $session_id = 0 if not defined $session_id;
2163 # Set FAI state to localboot
2164 my %mapActions= (
2165 reboot => '',
2166 update => 'softupdate',
2167 localboot => 'localboot',
2168 reinstall => 'install',
2169 rescan => '',
2170 wake => '',
2171 memcheck => 'memcheck',
2172 sysinfo => 'sysinfo',
2173 install => 'install',
2174 );
2176 # Return if this is unknown
2177 if (!exists $mapActions{ $st }){
2178 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2179 return;
2180 }
2182 my $state= $mapActions{ $st };
2184 my $ldap_handle = &get_ldap_handle($session_id);
2185 if( defined($ldap_handle) ) {
2187 # Build search filter for hosts
2188 my $search= "(&(objectClass=GOhard)";
2189 foreach (@{$targets}){
2190 $search.= "(macAddress=$_)";
2191 }
2192 $search.= ")";
2194 # If there's any host inside of the search string, procress them
2195 if (!($search =~ /macAddress/)){
2196 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2197 return;
2198 }
2200 # Perform search for Unit Tag
2201 my $mesg = $ldap_handle->search(
2202 base => $ldap_base,
2203 scope => 'sub',
2204 attrs => ['dn', 'FAIstate', 'objectClass'],
2205 filter => "$search"
2206 );
2208 if ($mesg->count) {
2209 my @entries = $mesg->entries;
2210 if (0 == @entries) {
2211 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2212 }
2214 foreach my $entry (@entries) {
2215 # Only modify entry if it is not set to '$state'
2216 if ($entry->get_value("FAIstate") ne "$state"){
2217 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2218 my $result;
2219 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2220 if (exists $tmp{'FAIobject'}){
2221 if ($state eq ''){
2222 $result= $ldap_handle->modify($entry->dn, changes => [
2223 delete => [ FAIstate => [] ] ]);
2224 } else {
2225 $result= $ldap_handle->modify($entry->dn, changes => [
2226 replace => [ FAIstate => $state ] ]);
2227 }
2228 } elsif ($state ne ''){
2229 $result= $ldap_handle->modify($entry->dn, changes => [
2230 add => [ objectClass => 'FAIobject' ],
2231 add => [ FAIstate => $state ] ]);
2232 }
2234 # Errors?
2235 if ($result->code){
2236 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2237 }
2238 } else {
2239 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2240 }
2241 }
2242 } else {
2243 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2244 }
2246 # if no ldap handle defined
2247 } else {
2248 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2249 }
2251 return;
2252 }
2255 sub change_goto_state {
2256 my ($st, $targets, $session_id) = @_;
2257 $session_id = 0 if not defined $session_id;
2259 # Switch on or off?
2260 my $state= $st eq 'active' ? 'active': 'locked';
2262 my $ldap_handle = &get_ldap_handle($session_id);
2263 if( defined($ldap_handle) ) {
2265 # Build search filter for hosts
2266 my $search= "(&(objectClass=GOhard)";
2267 foreach (@{$targets}){
2268 $search.= "(macAddress=$_)";
2269 }
2270 $search.= ")";
2272 # If there's any host inside of the search string, procress them
2273 if (!($search =~ /macAddress/)){
2274 return;
2275 }
2277 # Perform search for Unit Tag
2278 my $mesg = $ldap_handle->search(
2279 base => $ldap_base,
2280 scope => 'sub',
2281 attrs => ['dn', 'gotoMode'],
2282 filter => "$search"
2283 );
2285 if ($mesg->count) {
2286 my @entries = $mesg->entries;
2287 foreach my $entry (@entries) {
2289 # Only modify entry if it is not set to '$state'
2290 if ($entry->get_value("gotoMode") ne $state){
2292 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2293 my $result;
2294 $result= $ldap_handle->modify($entry->dn, changes => [
2295 replace => [ gotoMode => $state ] ]);
2297 # Errors?
2298 if ($result->code){
2299 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2300 }
2302 }
2303 }
2304 } else {
2305 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2306 }
2308 }
2309 }
2312 sub run_recreate_packages_db {
2313 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2314 my $session_id = $session->ID;
2315 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2316 $kernel->yield('create_fai_release_db', $fai_release_tn);
2317 $kernel->yield('create_fai_server_db', $fai_server_tn);
2318 return;
2319 }
2322 sub run_create_fai_server_db {
2323 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2324 my $session_id = $session->ID;
2325 my $task = POE::Wheel::Run->new(
2326 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2327 StdoutEvent => "session_run_result",
2328 StderrEvent => "session_run_debug",
2329 CloseEvent => "session_run_done",
2330 );
2332 $heap->{task}->{ $task->ID } = $task;
2333 return;
2334 }
2337 sub create_fai_server_db {
2338 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2339 my $result;
2341 if (not defined $session_id) { $session_id = 0; }
2342 my $ldap_handle = &get_ldap_handle();
2343 if(defined($ldap_handle)) {
2344 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2345 my $mesg= $ldap_handle->search(
2346 base => $ldap_base,
2347 scope => 'sub',
2348 attrs => ['FAIrepository', 'gosaUnitTag'],
2349 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2350 );
2351 if($mesg->{'resultCode'} == 0 &&
2352 $mesg->count != 0) {
2353 foreach my $entry (@{$mesg->{entries}}) {
2354 if($entry->exists('FAIrepository')) {
2355 # Add an entry for each Repository configured for server
2356 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2357 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2358 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2359 $result= $fai_server_db->add_dbentry( {
2360 table => $table_name,
2361 primkey => ['server', 'release', 'tag'],
2362 server => $tmp_url,
2363 release => $tmp_release,
2364 sections => $tmp_sections,
2365 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2366 } );
2367 }
2368 }
2369 }
2370 }
2371 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2373 # TODO: Find a way to post the 'create_packages_list_db' event
2374 if(not defined($dont_create_packages_list)) {
2375 &create_packages_list_db(undef, undef, $session_id);
2376 }
2377 }
2379 $ldap_handle->disconnect;
2380 return $result;
2381 }
2384 sub run_create_fai_release_db {
2385 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2386 my $session_id = $session->ID;
2387 my $task = POE::Wheel::Run->new(
2388 Program => sub { &create_fai_release_db($table_name, $session_id) },
2389 StdoutEvent => "session_run_result",
2390 StderrEvent => "session_run_debug",
2391 CloseEvent => "session_run_done",
2392 );
2394 $heap->{task}->{ $task->ID } = $task;
2395 return;
2396 }
2399 sub create_fai_release_db {
2400 my ($table_name, $session_id) = @_;
2401 my $result;
2403 # used for logging
2404 if (not defined $session_id) { $session_id = 0; }
2406 my $ldap_handle = &get_ldap_handle();
2407 if(defined($ldap_handle)) {
2408 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2409 my $mesg= $ldap_handle->search(
2410 base => $ldap_base,
2411 scope => 'sub',
2412 attrs => [],
2413 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2414 );
2415 if(($mesg->code == 0) && ($mesg->count != 0))
2416 {
2417 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2419 # Walk through all possible FAI container ou's
2420 my @sql_list;
2421 my $timestamp= &get_time();
2422 foreach my $ou (@{$mesg->{entries}}) {
2423 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2424 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2425 my @tmp_array=get_fai_release_entries($tmp_classes);
2426 if(@tmp_array) {
2427 foreach my $entry (@tmp_array) {
2428 if(defined($entry) && ref($entry) eq 'HASH') {
2429 my $sql=
2430 "INSERT INTO $table_name "
2431 ."(timestamp, release, class, type, state) VALUES ("
2432 .$timestamp.","
2433 ."'".$entry->{'release'}."',"
2434 ."'".$entry->{'class'}."',"
2435 ."'".$entry->{'type'}."',"
2436 ."'".$entry->{'state'}."')";
2437 push @sql_list, $sql;
2438 }
2439 }
2440 }
2441 }
2442 }
2444 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2445 if(@sql_list) {
2446 unshift @sql_list, "VACUUM";
2447 unshift @sql_list, "DELETE FROM $table_name";
2448 $fai_release_db->exec_statementlist(\@sql_list);
2449 }
2450 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2451 } else {
2452 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code ,5);
2453 }
2454 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2455 }
2456 $ldap_handle->disconnect;
2457 return $result;
2458 }
2460 sub get_fai_types {
2461 my $tmp_classes = shift || return undef;
2462 my @result;
2464 foreach my $type(keys %{$tmp_classes}) {
2465 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2466 my $entry = {
2467 type => $type,
2468 state => $tmp_classes->{$type}[0],
2469 };
2470 push @result, $entry;
2471 }
2472 }
2474 return @result;
2475 }
2477 sub get_fai_state {
2478 my $result = "";
2479 my $tmp_classes = shift || return $result;
2481 foreach my $type(keys %{$tmp_classes}) {
2482 if(defined($tmp_classes->{$type}[0])) {
2483 $result = $tmp_classes->{$type}[0];
2485 # State is equal for all types in class
2486 last;
2487 }
2488 }
2490 return $result;
2491 }
2493 sub resolve_fai_classes {
2494 my ($fai_base, $ldap_handle, $session_id) = @_;
2495 if (not defined $session_id) { $session_id = 0; }
2496 my $result;
2497 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2498 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2499 my $fai_classes;
2501 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2502 my $mesg= $ldap_handle->search(
2503 base => $fai_base,
2504 scope => 'sub',
2505 attrs => ['cn','objectClass','FAIstate'],
2506 filter => $fai_filter,
2507 );
2508 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2510 if($mesg->{'resultCode'} == 0 &&
2511 $mesg->count != 0) {
2512 foreach my $entry (@{$mesg->{entries}}) {
2513 if($entry->exists('cn')) {
2514 my $tmp_dn= $entry->dn();
2515 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2516 - length($fai_base) - 1 );
2518 # Skip classname and ou dn parts for class
2519 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2521 # Skip classes without releases
2522 if((!defined($tmp_release)) || length($tmp_release)==0) {
2523 next;
2524 }
2526 my $tmp_cn= $entry->get_value('cn');
2527 my $tmp_state= $entry->get_value('FAIstate');
2529 my $tmp_type;
2530 # Get FAI type
2531 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2532 if(grep $_ eq $oclass, @possible_fai_classes) {
2533 $tmp_type= $oclass;
2534 last;
2535 }
2536 }
2538 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2539 # A Subrelease
2540 my @sub_releases = split(/,/, $tmp_release);
2542 # Walk through subreleases and build hash tree
2543 my $hash;
2544 while(my $tmp_sub_release = pop @sub_releases) {
2545 $hash .= "\{'$tmp_sub_release'\}->";
2546 }
2547 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2548 } else {
2549 # A branch, no subrelease
2550 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2551 }
2552 } elsif (!$entry->exists('cn')) {
2553 my $tmp_dn= $entry->dn();
2554 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2555 - length($fai_base) - 1 );
2556 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2558 # Skip classes without releases
2559 if((!defined($tmp_release)) || length($tmp_release)==0) {
2560 next;
2561 }
2563 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2564 # A Subrelease
2565 my @sub_releases= split(/,/, $tmp_release);
2567 # Walk through subreleases and build hash tree
2568 my $hash;
2569 while(my $tmp_sub_release = pop @sub_releases) {
2570 $hash .= "\{'$tmp_sub_release'\}->";
2571 }
2572 # Remove the last two characters
2573 chop($hash);
2574 chop($hash);
2576 eval('$fai_classes->'.$hash.'= {}');
2577 } else {
2578 # A branch, no subrelease
2579 if(!exists($fai_classes->{$tmp_release})) {
2580 $fai_classes->{$tmp_release} = {};
2581 }
2582 }
2583 }
2584 }
2586 # The hash is complete, now we can honor the copy-on-write based missing entries
2587 foreach my $release (keys %$fai_classes) {
2588 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2589 }
2590 }
2591 return $result;
2592 }
2594 sub apply_fai_inheritance {
2595 my $fai_classes = shift || return {};
2596 my $tmp_classes;
2598 # Get the classes from the branch
2599 foreach my $class (keys %{$fai_classes}) {
2600 # Skip subreleases
2601 if($class =~ /^ou=.*$/) {
2602 next;
2603 } else {
2604 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2605 }
2606 }
2608 # Apply to each subrelease
2609 foreach my $subrelease (keys %{$fai_classes}) {
2610 if($subrelease =~ /ou=/) {
2611 foreach my $tmp_class (keys %{$tmp_classes}) {
2612 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2613 $fai_classes->{$subrelease}->{$tmp_class} =
2614 deep_copy($tmp_classes->{$tmp_class});
2615 } else {
2616 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2617 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2618 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2619 deep_copy($tmp_classes->{$tmp_class}->{$type});
2620 }
2621 }
2622 }
2623 }
2624 }
2625 }
2627 # Find subreleases in deeper levels
2628 foreach my $subrelease (keys %{$fai_classes}) {
2629 if($subrelease =~ /ou=/) {
2630 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2631 if($subsubrelease =~ /ou=/) {
2632 apply_fai_inheritance($fai_classes->{$subrelease});
2633 }
2634 }
2635 }
2636 }
2638 return $fai_classes;
2639 }
2641 sub get_fai_release_entries {
2642 my $tmp_classes = shift || return;
2643 my $parent = shift || "";
2644 my @result = shift || ();
2646 foreach my $entry (keys %{$tmp_classes}) {
2647 if(defined($entry)) {
2648 if($entry =~ /^ou=.*$/) {
2649 my $release_name = $entry;
2650 $release_name =~ s/ou=//g;
2651 if(length($parent)>0) {
2652 $release_name = $parent."/".$release_name;
2653 }
2654 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2655 foreach my $bufentry(@bufentries) {
2656 push @result, $bufentry;
2657 }
2658 } else {
2659 my @types = get_fai_types($tmp_classes->{$entry});
2660 foreach my $type (@types) {
2661 push @result,
2662 {
2663 'class' => $entry,
2664 'type' => $type->{'type'},
2665 'release' => $parent,
2666 'state' => $type->{'state'},
2667 };
2668 }
2669 }
2670 }
2671 }
2673 return @result;
2674 }
2676 sub deep_copy {
2677 my $this = shift;
2678 if (not ref $this) {
2679 $this;
2680 } elsif (ref $this eq "ARRAY") {
2681 [map deep_copy($_), @$this];
2682 } elsif (ref $this eq "HASH") {
2683 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2684 } else { die "what type is $_?" }
2685 }
2688 sub session_run_result {
2689 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2690 $kernel->sig(CHLD => "child_reap");
2691 }
2693 sub session_run_debug {
2694 my $result = $_[ARG0];
2695 print STDERR "$result\n";
2696 }
2698 sub session_run_done {
2699 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2700 delete $heap->{task}->{$task_id};
2701 }
2704 sub create_sources_list {
2705 my $session_id = shift;
2706 my $ldap_handle = &main::get_ldap_handle;
2707 my $result="/tmp/gosa_si_tmp_sources_list";
2709 # Remove old file
2710 if(stat($result)) {
2711 unlink($result);
2712 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2713 }
2715 my $fh;
2716 open($fh, ">$result");
2717 if (not defined $fh) {
2718 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2719 return undef;
2720 }
2721 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2722 my $mesg=$ldap_handle->search(
2723 base => $main::ldap_server_dn,
2724 scope => 'base',
2725 attrs => 'FAIrepository',
2726 filter => 'objectClass=FAIrepositoryServer'
2727 );
2728 if($mesg->count) {
2729 foreach my $entry(@{$mesg->{'entries'}}) {
2730 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2731 my ($server, $tag, $release, $sections)= split /\|/, $value;
2732 my $line = "deb $server $release";
2733 $sections =~ s/,/ /g;
2734 $line.= " $sections";
2735 print $fh $line."\n";
2736 }
2737 }
2738 }
2739 } else {
2740 if (defined $main::ldap_server_dn){
2741 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2742 } else {
2743 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2744 }
2745 }
2746 close($fh);
2748 return $result;
2749 }
2752 sub run_create_packages_list_db {
2753 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2754 my $session_id = $session->ID;
2756 my $task = POE::Wheel::Run->new(
2757 Priority => +20,
2758 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2759 StdoutEvent => "session_run_result",
2760 StderrEvent => "session_run_debug",
2761 CloseEvent => "session_run_done",
2762 );
2763 $heap->{task}->{ $task->ID } = $task;
2764 }
2767 sub create_packages_list_db {
2768 my ($ldap_handle, $sources_file, $session_id) = @_;
2770 # it should not be possible to trigger a recreation of packages_list_db
2771 # while packages_list_db is under construction, so set flag packages_list_under_construction
2772 # which is tested befor recreation can be started
2773 if (-r $packages_list_under_construction) {
2774 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2775 return;
2776 } else {
2777 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2778 # set packages_list_under_construction to true
2779 system("touch $packages_list_under_construction");
2780 @packages_list_statements=();
2781 }
2783 if (not defined $session_id) { $session_id = 0; }
2784 if (not defined $ldap_handle) {
2785 $ldap_handle= &get_ldap_handle();
2787 if (not defined $ldap_handle) {
2788 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2789 unlink($packages_list_under_construction);
2790 return;
2791 }
2792 }
2793 if (not defined $sources_file) {
2794 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2795 $sources_file = &create_sources_list($session_id);
2796 }
2798 if (not defined $sources_file) {
2799 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2800 unlink($packages_list_under_construction);
2801 return;
2802 }
2804 my $line;
2806 open(CONFIG, "<$sources_file") or do {
2807 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2808 unlink($packages_list_under_construction);
2809 return;
2810 };
2812 # Read lines
2813 while ($line = <CONFIG>){
2814 # Unify
2815 chop($line);
2816 $line =~ s/^\s+//;
2817 $line =~ s/^\s+/ /;
2819 # Strip comments
2820 $line =~ s/#.*$//g;
2822 # Skip empty lines
2823 if ($line =~ /^\s*$/){
2824 next;
2825 }
2827 # Interpret deb line
2828 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2829 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2830 my $section;
2831 foreach $section (split(' ', $sections)){
2832 &parse_package_info( $baseurl, $dist, $section, $session_id );
2833 }
2834 }
2835 }
2837 close (CONFIG);
2840 if(keys(%repo_dirs)) {
2841 find(\&cleanup_and_extract, keys( %repo_dirs ));
2842 &main::strip_packages_list_statements();
2843 unshift @packages_list_statements, "VACUUM";
2844 $packages_list_db->exec_statementlist(\@packages_list_statements);
2845 }
2846 unlink($packages_list_under_construction);
2847 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2848 return;
2849 }
2851 # This function should do some intensive task to minimize the db-traffic
2852 sub strip_packages_list_statements {
2853 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2854 my @new_statement_list=();
2855 my $hash;
2856 my $insert_hash;
2857 my $update_hash;
2858 my $delete_hash;
2859 my $local_timestamp=get_time();
2861 foreach my $existing_entry (@existing_entries) {
2862 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2863 }
2865 foreach my $statement (@packages_list_statements) {
2866 if($statement =~ /^INSERT/i) {
2867 # Assign the values from the insert statement
2868 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2869 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2870 if(exists($hash->{$distribution}->{$package}->{$version})) {
2871 # If section or description has changed, update the DB
2872 if(
2873 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2874 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2875 ) {
2876 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2877 }
2878 } else {
2879 # Insert a non-existing entry to db
2880 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2881 }
2882 } elsif ($statement =~ /^UPDATE/i) {
2883 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2884 /^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;
2885 foreach my $distribution (keys %{$hash}) {
2886 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2887 # update the insertion hash to execute only one query per package (insert instead insert+update)
2888 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2889 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2890 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2891 my $section;
2892 my $description;
2893 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2894 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2895 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2896 }
2897 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2898 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2899 }
2900 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2901 }
2902 }
2903 }
2904 }
2905 }
2907 # TODO: Check for orphaned entries
2909 # unroll the insert_hash
2910 foreach my $distribution (keys %{$insert_hash}) {
2911 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2912 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2913 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2914 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2915 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2916 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2917 ."'$local_timestamp')";
2918 }
2919 }
2920 }
2922 # unroll the update hash
2923 foreach my $distribution (keys %{$update_hash}) {
2924 foreach my $package (keys %{$update_hash->{$distribution}}) {
2925 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2926 my $set = "";
2927 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2928 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2929 }
2930 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2931 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2932 }
2933 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2934 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2935 }
2936 if(defined($set) and length($set) > 0) {
2937 $set .= "timestamp = '$local_timestamp'";
2938 } else {
2939 next;
2940 }
2941 push @new_statement_list,
2942 "UPDATE $main::packages_list_tn SET $set WHERE"
2943 ." distribution = '$distribution'"
2944 ." AND package = '$package'"
2945 ." AND version = '$version'";
2946 }
2947 }
2948 }
2950 @packages_list_statements = @new_statement_list;
2951 }
2954 sub parse_package_info {
2955 my ($baseurl, $dist, $section, $session_id)= @_;
2956 my ($package);
2957 if (not defined $session_id) { $session_id = 0; }
2958 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2959 $repo_dirs{ "${repo_path}/pool" } = 1;
2961 foreach $package ("Packages.gz"){
2962 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2963 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2964 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2965 }
2967 }
2970 sub get_package {
2971 my ($url, $dest, $session_id)= @_;
2972 if (not defined $session_id) { $session_id = 0; }
2974 my $tpath = dirname($dest);
2975 -d "$tpath" || mkpath "$tpath";
2977 # This is ugly, but I've no time to take a look at "how it works in perl"
2978 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2979 system("gunzip -cd '$dest' > '$dest.in'");
2980 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2981 unlink($dest);
2982 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2983 } else {
2984 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
2985 }
2986 return 0;
2987 }
2990 sub parse_package {
2991 my ($path, $dist, $srv_path, $session_id)= @_;
2992 if (not defined $session_id) { $session_id = 0;}
2993 my ($package, $version, $section, $description);
2994 my $PACKAGES;
2995 my $timestamp = &get_time();
2997 if(not stat("$path.in")) {
2998 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2999 return;
3000 }
3002 open($PACKAGES, "<$path.in");
3003 if(not defined($PACKAGES)) {
3004 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3005 return;
3006 }
3008 # Read lines
3009 while (<$PACKAGES>){
3010 my $line = $_;
3011 # Unify
3012 chop($line);
3014 # Use empty lines as a trigger
3015 if ($line =~ /^\s*$/){
3016 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3017 push(@packages_list_statements, $sql);
3018 $package = "none";
3019 $version = "none";
3020 $section = "none";
3021 $description = "none";
3022 next;
3023 }
3025 # Trigger for package name
3026 if ($line =~ /^Package:\s/){
3027 ($package)= ($line =~ /^Package: (.*)$/);
3028 next;
3029 }
3031 # Trigger for version
3032 if ($line =~ /^Version:\s/){
3033 ($version)= ($line =~ /^Version: (.*)$/);
3034 next;
3035 }
3037 # Trigger for description
3038 if ($line =~ /^Description:\s/){
3039 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3040 next;
3041 }
3043 # Trigger for section
3044 if ($line =~ /^Section:\s/){
3045 ($section)= ($line =~ /^Section: (.*)$/);
3046 next;
3047 }
3049 # Trigger for filename
3050 if ($line =~ /^Filename:\s/){
3051 my ($filename) = ($line =~ /^Filename: (.*)$/);
3052 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3053 next;
3054 }
3055 }
3057 close( $PACKAGES );
3058 unlink( "$path.in" );
3059 }
3062 sub store_fileinfo {
3063 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3065 my %fileinfo = (
3066 'package' => $package,
3067 'dist' => $dist,
3068 'version' => $vers,
3069 );
3071 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3072 }
3075 sub cleanup_and_extract {
3076 my $fileinfo = $repo_files{ $File::Find::name };
3078 if( defined $fileinfo ) {
3080 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3081 my $sql;
3082 my $package = $fileinfo->{ 'package' };
3083 my $newver = $fileinfo->{ 'version' };
3085 mkpath($dir);
3086 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3088 if( -f "$dir/DEBIAN/templates" ) {
3090 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3092 my $tmpl= "";
3093 {
3094 local $/=undef;
3095 open FILE, "$dir/DEBIAN/templates";
3096 $tmpl = &encode_base64(<FILE>);
3097 close FILE;
3098 }
3099 rmtree("$dir/DEBIAN/templates");
3101 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3102 push @packages_list_statements, $sql;
3103 }
3104 }
3106 return;
3107 }
3110 sub register_at_foreign_servers {
3111 my ($kernel) = $_[KERNEL];
3113 # hole alle bekannten server aus known_server_db
3114 my $server_sql = "SELECT * FROM $known_server_tn";
3115 my $server_res = $known_server_db->exec_statement($server_sql);
3117 # no entries in known_server_db
3118 if (not ref(@$server_res[0]) eq "ARRAY") {
3119 # TODO
3120 }
3122 # detect already connected clients
3123 my $client_sql = "SELECT * FROM $known_clients_tn";
3124 my $client_res = $known_clients_db->exec_statement($client_sql);
3126 # send my server details to all other gosa-si-server within the network
3127 foreach my $hit (@$server_res) {
3128 my $hostname = @$hit[0];
3129 my $hostkey = &create_passwd;
3131 # add already connected clients to registration message
3132 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3133 &add_content2xml_hash($myhash, 'key', $hostkey);
3134 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3136 # add locally loaded gosa-si modules to registration message
3137 my $loaded_modules = {};
3138 while (my ($package, $pck_info) = each %$known_modules) {
3139 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3140 foreach my $act_module (keys(%{@$pck_info[2]})) {
3141 $loaded_modules->{$act_module} = "";
3142 }
3143 }
3145 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3147 # add macaddress to registration message
3148 my ($host_ip, $host_port) = split(/:/, $hostname);
3149 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3150 my $network_interface= &get_interface_for_ip($local_ip);
3151 my $host_mac = &get_mac_for_interface($network_interface);
3152 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3154 # build registration message and send it
3155 my $foreign_server_msg = &create_xml_string($myhash);
3156 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3157 }
3159 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3160 return;
3161 }
3164 #==== MAIN = main ==============================================================
3165 # parse commandline options
3166 Getopt::Long::Configure( "bundling" );
3167 GetOptions("h|help" => \&usage,
3168 "c|config=s" => \$cfg_file,
3169 "f|foreground" => \$foreground,
3170 "v|verbose+" => \$verbose,
3171 "no-arp+" => \$no_arp,
3172 );
3174 # read and set config parameters
3175 &check_cmdline_param ;
3176 &read_configfile($cfg_file, %cfg_defaults);
3177 &check_pid;
3179 $SIG{CHLD} = 'IGNORE';
3181 # forward error messages to logfile
3182 if( ! $foreground ) {
3183 open( STDIN, '+>/dev/null' );
3184 open( STDOUT, '+>&STDIN' );
3185 open( STDERR, '+>&STDIN' );
3186 }
3188 # Just fork, if we are not in foreground mode
3189 if( ! $foreground ) {
3190 chdir '/' or die "Can't chdir to /: $!";
3191 $pid = fork;
3192 setsid or die "Can't start a new session: $!";
3193 umask 0;
3194 } else {
3195 $pid = $$;
3196 }
3198 # Do something useful - put our PID into the pid_file
3199 if( 0 != $pid ) {
3200 open( LOCK_FILE, ">$pid_file" );
3201 print LOCK_FILE "$pid\n";
3202 close( LOCK_FILE );
3203 if( !$foreground ) {
3204 exit( 0 )
3205 };
3206 }
3208 # parse head url and revision from svn
3209 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3210 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3211 $server_headURL = defined $1 ? $1 : 'unknown' ;
3212 $server_revision = defined $2 ? $2 : 'unknown' ;
3213 if ($server_headURL =~ /\/tag\// ||
3214 $server_headURL =~ /\/branches\// ) {
3215 $server_status = "stable";
3216 } else {
3217 $server_status = "developmental" ;
3218 }
3220 # Prepare log file
3221 $root_uid = getpwnam('root');
3222 $adm_gid = getgrnam('adm');
3223 chmod(0640, $log_file);
3224 chown($root_uid, $adm_gid, $log_file);
3225 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3227 daemon_log(" ", 1);
3228 daemon_log("$0 started!", 1);
3229 daemon_log("status: $server_status", 1);
3230 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3232 # connect to incoming_db
3233 unlink($incoming_file_name);
3234 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3235 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3237 # connect to gosa-si job queue
3238 unlink($job_queue_file_name); ## just for debugging
3239 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3240 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3241 chmod(0660, $job_queue_file_name);
3242 chown($root_uid, $adm_gid, $job_queue_file_name);
3244 # connect to known_clients_db
3245 unlink($known_clients_file_name); ## just for debugging
3246 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3247 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3248 chmod(0660, $known_clients_file_name);
3249 chown($root_uid, $adm_gid, $known_clients_file_name);
3251 # connect to foreign_clients_db
3252 unlink($foreign_clients_file_name);
3253 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3254 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3255 chmod(0660, $foreign_clients_file_name);
3256 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3258 # connect to known_server_db
3259 unlink($known_server_file_name);
3260 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3261 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3262 chmod(0660, $known_server_file_name);
3263 chown($root_uid, $adm_gid, $known_server_file_name);
3265 # connect to login_usr_db
3266 unlink($login_users_file_name);
3267 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3268 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3269 chmod(0660, $login_users_file_name);
3270 chown($root_uid, $adm_gid, $login_users_file_name);
3272 # connect to fai_server_db
3273 unlink($fai_server_file_name);
3274 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3275 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3276 chmod(0660, $fai_server_file_name);
3277 chown($root_uid, $adm_gid, $fai_server_file_name);
3279 # connect to fai_release_db
3280 unlink($fai_release_file_name);
3281 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3282 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3283 chmod(0660, $fai_release_file_name);
3284 chown($root_uid, $adm_gid, $fai_release_file_name);
3286 # connect to packages_list_db
3287 #unlink($packages_list_file_name);
3288 unlink($packages_list_under_construction);
3289 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3290 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3291 chmod(0660, $packages_list_file_name);
3292 chown($root_uid, $adm_gid, $packages_list_file_name);
3294 # connect to messaging_db
3295 unlink($messaging_file_name);
3296 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3297 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3298 chmod(0660, $messaging_file_name);
3299 chown($root_uid, $adm_gid, $messaging_file_name);
3302 # create xml object used for en/decrypting
3303 $xml = new XML::Simple();
3306 # foreign servers
3307 my @foreign_server_list;
3309 # add foreign server from cfg file
3310 if ($foreign_server_string ne "") {
3311 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3312 foreach my $foreign_server (@cfg_foreign_server_list) {
3313 push(@foreign_server_list, $foreign_server);
3314 }
3316 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3317 }
3319 # Perform a DNS lookup for server registration if flag is true
3320 if ($dns_lookup eq "true") {
3321 # Add foreign server from dns
3322 my @tmp_servers;
3323 if (not $server_domain) {
3324 # Try our DNS Searchlist
3325 for my $domain(get_dns_domains()) {
3326 chomp($domain);
3327 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3328 if(@$tmp_domains) {
3329 for my $tmp_server(@$tmp_domains) {
3330 push @tmp_servers, $tmp_server;
3331 }
3332 }
3333 }
3334 if(@tmp_servers && length(@tmp_servers)==0) {
3335 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3336 }
3337 } else {
3338 @tmp_servers = &get_server_addresses($server_domain);
3339 if( 0 == @tmp_servers ) {
3340 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3341 }
3342 }
3344 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3346 foreach my $server (@tmp_servers) {
3347 unshift(@foreign_server_list, $server);
3348 }
3349 } else {
3350 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3351 }
3354 # eliminate duplicate entries
3355 @foreign_server_list = &del_doubles(@foreign_server_list);
3356 my $all_foreign_server = join(", ", @foreign_server_list);
3357 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3359 # add all found foreign servers to known_server
3360 my $act_timestamp = &get_time();
3361 foreach my $foreign_server (@foreign_server_list) {
3363 # do not add myself to known_server_db
3364 if (&is_local($foreign_server)) { next; }
3365 ######################################
3367 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3368 primkey=>['hostname'],
3369 hostname=>$foreign_server,
3370 macaddress=>"",
3371 status=>'not_jet_registered',
3372 hostkey=>"none",
3373 loaded_modules => "none",
3374 timestamp=>$act_timestamp,
3375 } );
3376 }
3379 # Import all modules
3380 &import_modules;
3382 # Check wether all modules are gosa-si valid passwd check
3383 &password_check;
3385 # Prepare for using Opsi
3386 if ($opsi_enabled eq "true") {
3387 use JSON::RPC::Client;
3388 use XML::Quote qw(:all);
3389 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3390 $opsi_client = new JSON::RPC::Client;
3391 }
3394 POE::Component::Server::TCP->new(
3395 Alias => "TCP_SERVER",
3396 Port => $server_port,
3397 ClientInput => sub {
3398 my ($kernel, $input) = @_[KERNEL, ARG0];
3399 push(@tasks, $input);
3400 push(@msgs_to_decrypt, $input);
3401 $kernel->yield("msg_to_decrypt");
3402 },
3403 InlineStates => {
3404 msg_to_decrypt => \&msg_to_decrypt,
3405 next_task => \&next_task,
3406 task_result => \&handle_task_result,
3407 task_done => \&handle_task_done,
3408 task_debug => \&handle_task_debug,
3409 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3410 }
3411 );
3413 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3415 # create session for repeatedly checking the job queue for jobs
3416 POE::Session->create(
3417 inline_states => {
3418 _start => \&session_start,
3419 register_at_foreign_servers => \®ister_at_foreign_servers,
3420 sig_handler => \&sig_handler,
3421 next_task => \&next_task,
3422 task_result => \&handle_task_result,
3423 task_done => \&handle_task_done,
3424 task_debug => \&handle_task_debug,
3425 watch_for_next_tasks => \&watch_for_next_tasks,
3426 watch_for_new_messages => \&watch_for_new_messages,
3427 watch_for_delivery_messages => \&watch_for_delivery_messages,
3428 watch_for_done_messages => \&watch_for_done_messages,
3429 watch_for_new_jobs => \&watch_for_new_jobs,
3430 watch_for_modified_jobs => \&watch_for_modified_jobs,
3431 watch_for_done_jobs => \&watch_for_done_jobs,
3432 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3433 watch_for_old_known_clients => \&watch_for_old_known_clients,
3434 create_packages_list_db => \&run_create_packages_list_db,
3435 create_fai_server_db => \&run_create_fai_server_db,
3436 create_fai_release_db => \&run_create_fai_release_db,
3437 recreate_packages_db => \&run_recreate_packages_db,
3438 session_run_result => \&session_run_result,
3439 session_run_debug => \&session_run_debug,
3440 session_run_done => \&session_run_done,
3441 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3442 }
3443 );
3446 POE::Kernel->run();
3447 exit;