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::DBmysql;
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 ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
104 our $known_modules;
105 our $root_uid;
106 our $adm_gid;
109 # specifies the verbosity of the daemon_log
110 $verbose = 0 ;
112 # if foreground is not null, script will be not forked to background
113 $foreground = 0 ;
115 # specifies the timeout seconds while checking the online status of a registrating client
116 $ping_timeout = 5;
118 $no_arp = 0;
119 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
120 my @packages_list_statements;
121 my $watch_for_new_jobs_in_progress = 0;
123 # holds all incoming decrypted messages
124 our $incoming_db;
125 our $incoming_tn = 'incoming';
126 my $incoming_file_name;
127 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
128 "timestamp VARCHAR(14) DEFAULT 'none'",
129 "headertag VARCHAR(255) DEFAULT 'none'",
130 "targettag VARCHAR(255) DEFAULT 'none'",
131 "xmlmessage TEXT",
132 "module VARCHAR(255) DEFAULT 'none'",
133 "sessionid VARCHAR(255) DEFAULT '0'",
134 );
136 # holds all gosa jobs
137 our $job_db;
138 our $job_queue_tn = 'jobs';
139 my $job_queue_file_name;
140 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
141 "timestamp VARCHAR(14) DEFAULT 'none'",
142 "status VARCHAR(255) DEFAULT 'none'",
143 "result TEXT",
144 "progress VARCHAR(255) DEFAULT 'none'",
145 "headertag VARCHAR(255) DEFAULT 'none'",
146 "targettag VARCHAR(255) DEFAULT 'none'",
147 "xmlmessage TEXT",
148 "macaddress VARCHAR(17) DEFAULT 'none'",
149 "plainname VARCHAR(255) DEFAULT 'none'",
150 "siserver VARCHAR(255) DEFAULT 'none'",
151 "modified INTEGER DEFAULT '0'",
152 );
154 # holds all other gosa-si-server
155 our $known_server_db;
156 our $known_server_tn = "known_server";
157 my $known_server_file_name;
158 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
160 # holds all registrated clients
161 our $known_clients_db;
162 our $known_clients_tn = "known_clients";
163 my $known_clients_file_name;
164 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
166 # holds all registered clients at a foreign server
167 our $foreign_clients_db;
168 our $foreign_clients_tn = "foreign_clients";
169 my $foreign_clients_file_name;
170 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
172 # holds all logged in user at each client
173 our $login_users_db;
174 our $login_users_tn = "login_users";
175 my $login_users_file_name;
176 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)");
178 # holds all fai server, the debian release and tag
179 our $fai_server_db;
180 our $fai_server_tn = "fai_server";
181 my $fai_server_file_name;
182 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
184 our $fai_release_db;
185 our $fai_release_tn = "fai_release";
186 my $fai_release_file_name;
187 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
189 # holds all packages available from different repositories
190 our $packages_list_db;
191 our $packages_list_tn = "packages_list";
192 my $packages_list_file_name;
193 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
194 my $outdir = "/tmp/packages_list_db";
195 my $arch = "i386";
197 # holds all messages which should be delivered to a user
198 our $messaging_db;
199 our $messaging_tn = "messaging";
200 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
201 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
202 my $messaging_file_name;
204 # path to directory to store client install log files
205 our $client_fai_log_dir = "/var/log/fai";
207 # queue which stores taskes until one of the $max_children children are ready to process the task
208 #my @tasks = qw();
209 my @msgs_to_decrypt = qw();
210 my $max_children = 2;
213 # loop delay for job queue to look for opsi jobs
214 my $job_queue_opsi_delay = 10;
215 our $opsi_client;
216 our $opsi_url;
218 # Lifetime of logged in user information. If no update information comes after n seconds,
219 # the user is expeceted to be no longer logged in or the host is no longer running. Because
220 # of this, the user is deleted from login_users_db
221 our $logged_in_user_date_of_expiry = 600;
224 %cfg_defaults = (
225 "general" => {
226 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
227 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
228 },
229 "server" => {
230 "ip" => [\$server_ip, "0.0.0.0"],
231 "port" => [\$server_port, "20081"],
232 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
233 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
234 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
235 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
236 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
237 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
238 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
239 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
240 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
241 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
242 "repo-path" => [\$repo_path, '/srv/www/repository'],
243 "ldap-uri" => [\$ldap_uri, ""],
244 "ldap-base" => [\$ldap_base, ""],
245 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
246 "ldap-admin-password" => [\$ldap_admin_password, ""],
247 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
248 "max-clients" => [\$max_clients, 10],
249 "wol-password" => [\$wake_on_lan_passwd, ""],
250 "mysql-username" => [\$mysql_username, "gosa_si"],
251 "mysql-password" => [\$mysql_password, ""],
252 "mysql-database" => [\$mysql_database, "gosa_si"],
253 "mysql-host" => [\$mysql_host, "127.0.0.1"],
254 },
255 "GOsaPackages" => {
256 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
257 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
258 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
259 "key" => [\$GosaPackages_key, "none"],
260 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
261 },
262 "ClientPackages" => {
263 "key" => [\$ClientPackages_key, "none"],
264 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
265 },
266 "ServerPackages"=> {
267 "address" => [\$foreign_server_string, ""],
268 "dns-lookup" => [\$dns_lookup, "true"],
269 "domain" => [\$server_domain, ""],
270 "key" => [\$ServerPackages_key, "none"],
271 "key-lifetime" => [\$foreign_servers_register_delay, 120],
272 "job-synchronization-enabled" => [\$job_synchronization, "true"],
273 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
274 },
275 "ArpHandler" => {
276 "enabled" => [\$arp_enabled, "true"],
277 "interface" => [\$arp_interface, "all"],
278 },
279 "Opsi" => {
280 "enabled" => [\$opsi_enabled, "false"],
281 "server" => [\$opsi_server, "localhost"],
282 "admin" => [\$opsi_admin, "opsi-admin"],
283 "password" => [\$opsi_password, "secret"],
284 },
286 );
289 #=== FUNCTION ================================================================
290 # NAME: usage
291 # PARAMETERS: nothing
292 # RETURNS: nothing
293 # DESCRIPTION: print out usage text to STDERR
294 #===============================================================================
295 sub usage {
296 print STDERR << "EOF" ;
297 usage: $prg [-hvf] [-c config]
299 -h : this (help) message
300 -c <file> : config file
301 -f : foreground, process will not be forked to background
302 -v : be verbose (multiple to increase verbosity)
303 -no-arp : starts $prg without connection to arp module
305 EOF
306 print "\n" ;
307 }
310 #=== FUNCTION ================================================================
311 # NAME: logging
312 # PARAMETERS: level - string - default 'info'
313 # msg - string -
314 # facility - string - default 'LOG_DAEMON'
315 # RETURNS: nothing
316 # DESCRIPTION: function for logging
317 #===============================================================================
318 sub daemon_log {
319 # log into log_file
320 my( $msg, $level ) = @_;
321 if(not defined $msg) { return }
322 if(not defined $level) { $level = 1 }
323 if(defined $log_file){
324 open(LOG_HANDLE, ">>$log_file");
325 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
326 print STDERR "cannot open $log_file: $!";
327 return
328 }
329 chomp($msg);
330 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
331 if($level <= $verbose){
332 my ($seconds, $minutes, $hours, $monthday, $month,
333 $year, $weekday, $yearday, $sommertime) = localtime(time);
334 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
335 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
336 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
337 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
338 $month = $monthnames[$month];
339 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
340 $year+=1900;
341 my $name = $prg;
343 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
344 print LOG_HANDLE $log_msg;
345 if( $foreground ) {
346 print STDERR $log_msg;
347 }
348 }
349 close( LOG_HANDLE );
350 }
351 }
354 #=== FUNCTION ================================================================
355 # NAME: check_cmdline_param
356 # PARAMETERS: nothing
357 # RETURNS: nothing
358 # DESCRIPTION: validates commandline parameter
359 #===============================================================================
360 sub check_cmdline_param () {
361 my $err_config;
362 my $err_counter = 0;
363 if(not defined($cfg_file)) {
364 $cfg_file = "/etc/gosa-si/server.conf";
365 if(! -r $cfg_file) {
366 $err_config = "please specify a config file";
367 $err_counter += 1;
368 }
369 }
370 if( $err_counter > 0 ) {
371 &usage( "", 1 );
372 if( defined( $err_config)) { print STDERR "$err_config\n"}
373 print STDERR "\n";
374 exit( -1 );
375 }
376 }
379 #=== FUNCTION ================================================================
380 # NAME: check_pid
381 # PARAMETERS: nothing
382 # RETURNS: nothing
383 # DESCRIPTION: handels pid processing
384 #===============================================================================
385 sub check_pid {
386 $pid = -1;
387 # Check, if we are already running
388 if( open(LOCK_FILE, "<$pid_file") ) {
389 $pid = <LOCK_FILE>;
390 if( defined $pid ) {
391 chomp( $pid );
392 if( -f "/proc/$pid/stat" ) {
393 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
394 if( $stat ) {
395 daemon_log("ERROR: Already running",1);
396 close( LOCK_FILE );
397 exit -1;
398 }
399 }
400 }
401 close( LOCK_FILE );
402 unlink( $pid_file );
403 }
405 # create a syslog msg if it is not to possible to open PID file
406 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
407 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
408 if (open(LOCK_FILE, '<', $pid_file)
409 && ($pid = <LOCK_FILE>))
410 {
411 chomp($pid);
412 $msg .= "(PID $pid)\n";
413 } else {
414 $msg .= "(unable to read PID)\n";
415 }
416 if( ! ($foreground) ) {
417 openlog( $0, "cons,pid", "daemon" );
418 syslog( "warning", $msg );
419 closelog();
420 }
421 else {
422 print( STDERR " $msg " );
423 }
424 exit( -1 );
425 }
426 }
428 #=== FUNCTION ================================================================
429 # NAME: import_modules
430 # PARAMETERS: module_path - string - abs. path to the directory the modules
431 # are stored
432 # RETURNS: nothing
433 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
434 # state is on is imported by "require 'file';"
435 #===============================================================================
436 sub import_modules {
437 daemon_log(" ", 1);
439 if (not -e $modules_path) {
440 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
441 }
443 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
444 while (defined (my $file = readdir (DIR))) {
445 if (not $file =~ /(\S*?).pm$/) {
446 next;
447 }
448 my $mod_name = $1;
450 # ArpHandler switch
451 if( $file =~ /ArpHandler.pm/ ) {
452 if( $arp_enabled eq "false" ) { next; }
453 }
455 eval { require $file; };
456 if ($@) {
457 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
458 daemon_log("$@", 1);
459 exit;
460 } else {
461 my $info = eval($mod_name.'::get_module_info()');
462 # Only load module if get_module_info() returns a non-null object
463 if( $info ) {
464 my ($input_address, $input_key, $event_hash) = @{$info};
465 $known_modules->{$mod_name} = $info;
466 daemon_log("0 INFO: module $mod_name loaded", 5);
467 }
468 }
469 }
471 close (DIR);
472 }
474 #=== FUNCTION ================================================================
475 # NAME: password_check
476 # PARAMETERS: nothing
477 # RETURNS: nothing
478 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
479 # the same password
480 #===============================================================================
481 sub password_check {
482 my $passwd_hash = {};
483 while (my ($mod_name, $mod_info) = each %$known_modules) {
484 my $mod_passwd = @$mod_info[1];
485 if (not defined $mod_passwd) { next; }
486 if (not exists $passwd_hash->{$mod_passwd}) {
487 $passwd_hash->{$mod_passwd} = $mod_name;
489 # escalates critical error
490 } else {
491 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
492 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
493 exit( -1 );
494 }
495 }
497 }
500 #=== FUNCTION ================================================================
501 # NAME: sig_int_handler
502 # PARAMETERS: signal - string - signal arose from system
503 # RETURNS: nothing
504 # DESCRIPTION: handels tasks to be done befor signal becomes active
505 #===============================================================================
506 sub sig_int_handler {
507 my ($signal) = @_;
509 # if (defined($ldap_handle)) {
510 # $ldap_handle->disconnect;
511 # }
512 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
515 daemon_log("shutting down gosa-si-server", 1);
516 system("kill `ps -C gosa-si-server -o pid=`");
517 }
518 $SIG{INT} = \&sig_int_handler;
521 sub check_key_and_xml_validity {
522 my ($crypted_msg, $module_key, $session_id) = @_;
523 my $msg;
524 my $msg_hash;
525 my $error_string;
526 eval{
527 $msg = &decrypt_msg($crypted_msg, $module_key);
529 if ($msg =~ /<xml>/i){
530 $msg =~ s/\s+/ /g; # just for better daemon_log
531 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
532 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
534 ##############
535 # check header
536 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
537 my $header_l = $msg_hash->{'header'};
538 if( 1 > @{$header_l} ) { die 'empty header tag'; }
539 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
540 my $header = @{$header_l}[0];
541 if( 0 == length $header) { die 'empty string in header tag'; }
543 ##############
544 # check source
545 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
546 my $source_l = $msg_hash->{'source'};
547 if( 1 > @{$source_l} ) { die 'empty source tag'; }
548 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
549 my $source = @{$source_l}[0];
550 if( 0 == length $source) { die 'source error'; }
552 ##############
553 # check target
554 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
555 my $target_l = $msg_hash->{'target'};
556 if( 1 > @{$target_l} ) { die 'empty target tag'; }
557 }
558 };
559 if($@) {
560 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
561 $msg = undef;
562 $msg_hash = undef;
563 }
565 return ($msg, $msg_hash);
566 }
569 sub check_outgoing_xml_validity {
570 my ($msg, $session_id) = @_;
572 my $msg_hash;
573 eval{
574 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
576 ##############
577 # check header
578 my $header_l = $msg_hash->{'header'};
579 if( 1 != @{$header_l} ) {
580 die 'no or more than one headers specified';
581 }
582 my $header = @{$header_l}[0];
583 if( 0 == length $header) {
584 die 'header has length 0';
585 }
587 ##############
588 # check source
589 my $source_l = $msg_hash->{'source'};
590 if( 1 != @{$source_l} ) {
591 die 'no or more than 1 sources specified';
592 }
593 my $source = @{$source_l}[0];
594 if( 0 == length $source) {
595 die 'source has length 0';
596 }
597 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
598 $source =~ /^GOSA$/i ) {
599 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
600 }
602 ##############
603 # check target
604 my $target_l = $msg_hash->{'target'};
605 if( 0 == @{$target_l} ) {
606 die "no targets specified";
607 }
608 foreach my $target (@$target_l) {
609 if( 0 == length $target) {
610 die "target has length 0";
611 }
612 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
613 $target =~ /^GOSA$/i ||
614 $target =~ /^\*$/ ||
615 $target =~ /KNOWN_SERVER/i ||
616 $target =~ /JOBDB/i ||
617 $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 ){
618 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
619 }
620 }
621 };
622 if($@) {
623 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
624 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
625 $msg_hash = undef;
626 }
628 return ($msg_hash);
629 }
632 sub input_from_known_server {
633 my ($input, $remote_ip, $session_id) = @_ ;
634 my ($msg, $msg_hash, $module);
636 my $sql_statement= "SELECT * FROM known_server";
637 my $query_res = $known_server_db->select_dbentry( $sql_statement );
639 while( my ($hit_num, $hit) = each %{ $query_res } ) {
640 my $host_name = $hit->{hostname};
641 if( not $host_name =~ "^$remote_ip") {
642 next;
643 }
644 my $host_key = $hit->{hostkey};
645 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
646 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
648 # check if module can open msg envelope with module key
649 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
650 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
651 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
652 daemon_log("$@", 8);
653 next;
654 }
655 else {
656 $msg = $tmp_msg;
657 $msg_hash = $tmp_msg_hash;
658 $module = "ServerPackages";
659 last;
660 }
661 }
663 if( (!$msg) || (!$msg_hash) || (!$module) ) {
664 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
665 }
667 return ($msg, $msg_hash, $module);
668 }
671 sub input_from_known_client {
672 my ($input, $remote_ip, $session_id) = @_ ;
673 my ($msg, $msg_hash, $module);
675 my $sql_statement= "SELECT * FROM known_clients";
676 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
677 while( my ($hit_num, $hit) = each %{ $query_res } ) {
678 my $host_name = $hit->{hostname};
679 if( not $host_name =~ /^$remote_ip:\d*$/) {
680 next;
681 }
682 my $host_key = $hit->{hostkey};
683 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
684 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
686 # check if module can open msg envelope with module key
687 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
689 if( (!$msg) || (!$msg_hash) ) {
690 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
691 &daemon_log("$@", 8);
692 next;
693 }
694 else {
695 $module = "ClientPackages";
696 last;
697 }
698 }
700 if( (!$msg) || (!$msg_hash) || (!$module) ) {
701 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
702 }
704 return ($msg, $msg_hash, $module);
705 }
708 sub input_from_unknown_host {
709 no strict "refs";
710 my ($input, $session_id) = @_ ;
711 my ($msg, $msg_hash, $module);
712 my $error_string;
714 my %act_modules = %$known_modules;
716 while( my ($mod, $info) = each(%act_modules)) {
718 # check a key exists for this module
719 my $module_key = ${$mod."_key"};
720 if( not defined $module_key ) {
721 if( $mod eq 'ArpHandler' ) {
722 next;
723 }
724 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
725 next;
726 }
727 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
729 # check if module can open msg envelope with module key
730 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
731 if( (not defined $msg) || (not defined $msg_hash) ) {
732 next;
733 } else {
734 $module = $mod;
735 last;
736 }
737 }
739 if( (!$msg) || (!$msg_hash) || (!$module)) {
740 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
741 }
743 return ($msg, $msg_hash, $module);
744 }
747 sub create_ciphering {
748 my ($passwd) = @_;
749 if((!defined($passwd)) || length($passwd)==0) {
750 $passwd = "";
751 }
752 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
753 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
754 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
755 $my_cipher->set_iv($iv);
756 return $my_cipher;
757 }
760 sub encrypt_msg {
761 my ($msg, $key) = @_;
762 my $my_cipher = &create_ciphering($key);
763 my $len;
764 {
765 use bytes;
766 $len= 16-length($msg)%16;
767 }
768 $msg = "\0"x($len).$msg;
769 $msg = $my_cipher->encrypt($msg);
770 chomp($msg = &encode_base64($msg));
771 # there are no newlines allowed inside msg
772 $msg=~ s/\n//g;
773 return $msg;
774 }
777 sub decrypt_msg {
779 my ($msg, $key) = @_ ;
780 $msg = &decode_base64($msg);
781 my $my_cipher = &create_ciphering($key);
782 $msg = $my_cipher->decrypt($msg);
783 $msg =~ s/\0*//g;
784 return $msg;
785 }
788 sub get_encrypt_key {
789 my ($target) = @_ ;
790 my $encrypt_key;
791 my $error = 0;
793 # target can be in known_server
794 if( not defined $encrypt_key ) {
795 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
796 my $query_res = $known_server_db->select_dbentry( $sql_statement );
797 while( my ($hit_num, $hit) = each %{ $query_res } ) {
798 my $host_name = $hit->{hostname};
799 if( $host_name ne $target ) {
800 next;
801 }
802 $encrypt_key = $hit->{hostkey};
803 last;
804 }
805 }
807 # target can be in known_client
808 if( not defined $encrypt_key ) {
809 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
810 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
811 while( my ($hit_num, $hit) = each %{ $query_res } ) {
812 my $host_name = $hit->{hostname};
813 if( $host_name ne $target ) {
814 next;
815 }
816 $encrypt_key = $hit->{hostkey};
817 last;
818 }
819 }
821 return $encrypt_key;
822 }
825 #=== FUNCTION ================================================================
826 # NAME: open_socket
827 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
828 # [PeerPort] string necessary if port not appended by PeerAddr
829 # RETURNS: socket IO::Socket::INET
830 # DESCRIPTION: open a socket to PeerAddr
831 #===============================================================================
832 sub open_socket {
833 my ($PeerAddr, $PeerPort) = @_ ;
834 if(defined($PeerPort)){
835 $PeerAddr = $PeerAddr.":".$PeerPort;
836 }
837 my $socket;
838 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
839 Porto => "tcp",
840 Type => SOCK_STREAM,
841 Timeout => 5,
842 );
843 if(not defined $socket) {
844 return;
845 }
846 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
847 return $socket;
848 }
851 #sub get_local_ip_for_remote_ip {
852 # my $remote_ip= shift;
853 # my $result="0.0.0.0";
854 #
855 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
856 # if($remote_ip eq "127.0.0.1") {
857 # $result = "127.0.0.1";
858 # } else {
859 # my $PROC_NET_ROUTE= ('/proc/net/route');
860 #
861 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
862 # or die "Could not open $PROC_NET_ROUTE";
863 #
864 # my @ifs = <PROC_NET_ROUTE>;
865 #
866 # close(PROC_NET_ROUTE);
867 #
868 # # Eat header line
869 # shift @ifs;
870 # chomp @ifs;
871 # foreach my $line(@ifs) {
872 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
873 # my $destination;
874 # my $mask;
875 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
876 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
877 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
878 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
879 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
880 # # destination matches route, save mac and exit
881 # $result= &get_ip($Iface);
882 # last;
883 # }
884 # }
885 # }
886 # } else {
887 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
888 # }
889 # return $result;
890 #}
893 sub send_msg_to_target {
894 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
895 my $error = 0;
896 my $header;
897 my $timestamp = &get_time();
898 my $new_status;
899 my $act_status;
900 my ($sql_statement, $res);
902 if( $msg_header ) {
903 $header = "'$msg_header'-";
904 } else {
905 $header = "";
906 }
908 # Patch the source ip
909 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
910 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
911 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
912 }
914 # encrypt xml msg
915 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
917 # opensocket
918 my $socket = &open_socket($address);
919 if( !$socket ) {
920 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
921 $error++;
922 }
924 if( $error == 0 ) {
925 # send xml msg
926 print $socket $crypted_msg."\n";
928 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
929 daemon_log("$session_id DEBUG: message:\n$msg", 9);
931 }
933 # close socket in any case
934 if( $socket ) {
935 close $socket;
936 }
938 if( $error > 0 ) { $new_status = "down"; }
939 else { $new_status = $msg_header; }
942 # known_clients
943 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
944 $res = $known_clients_db->select_dbentry($sql_statement);
945 if( keys(%$res) == 1) {
946 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
947 if ($act_status eq "down" && $new_status eq "down") {
948 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
949 $res = $known_clients_db->del_dbentry($sql_statement);
950 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
951 } else {
952 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
953 $res = $known_clients_db->update_dbentry($sql_statement);
954 if($new_status eq "down"){
955 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
956 } else {
957 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
958 }
959 }
960 }
962 # known_server
963 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
964 $res = $known_server_db->select_dbentry($sql_statement);
965 if( keys(%$res) == 1) {
966 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967 if ($act_status eq "down" && $new_status eq "down") {
968 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
969 $res = $known_server_db->del_dbentry($sql_statement);
970 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
971 }
972 else {
973 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
974 $res = $known_server_db->update_dbentry($sql_statement);
975 if($new_status eq "down"){
976 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
977 } else {
978 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
979 }
980 }
981 }
982 return $error;
983 }
986 sub update_jobdb_status_for_send_msgs {
987 my ($answer, $error) = @_;
988 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
989 my $jobdb_id = $1;
991 # sending msg faild
992 if( $error ) {
993 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
994 my $sql_statement = "UPDATE $job_queue_tn ".
995 "SET status='error', result='can not deliver msg, please consult log file' ".
996 "WHERE id=$jobdb_id";
997 my $res = $job_db->update_dbentry($sql_statement);
998 }
1000 # sending msg was successful
1001 } else {
1002 my $sql_statement = "UPDATE $job_queue_tn ".
1003 "SET status='done' ".
1004 "WHERE id=$jobdb_id AND status='processed'";
1005 my $res = $job_db->update_dbentry($sql_statement);
1006 }
1007 }
1008 }
1011 sub sig_handler {
1012 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013 daemon_log("0 INFO got signal '$signal'", 1);
1014 $kernel->sig_handled();
1015 return;
1016 }
1019 sub msg_to_decrypt {
1020 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1021 my $session_id = $session->ID;
1022 my ($msg, $msg_hash, $module);
1023 my $error = 0;
1025 # hole neue msg aus @msgs_to_decrypt
1026 my $next_msg = shift @msgs_to_decrypt;
1028 # entschlüssle sie
1030 # msg is from a new client or gosa
1031 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032 # msg is from a gosa-si-server
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035 }
1036 # msg is from a gosa-si-client
1037 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039 }
1040 # an error occurred
1041 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043 # could not understand a msg from its server the client cause a re-registering process
1044 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1045 "' to cause a re-registering of the client if necessary", 3);
1046 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1047 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1048 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1049 my $host_name = $hit->{'hostname'};
1050 my $host_key = $hit->{'hostkey'};
1051 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1052 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1053 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1054 }
1055 $error++;
1056 }
1059 my $header;
1060 my $target;
1061 my $source;
1062 my $done = 0;
1063 my $sql;
1064 my $res;
1066 # check whether this message should be processed here
1067 if ($error == 0) {
1068 $header = @{$msg_hash->{'header'}}[0];
1069 $target = @{$msg_hash->{'target'}}[0];
1070 $source = @{$msg_hash->{'source'}}[0];
1071 my $not_found_in_known_clients_db = 0;
1072 my $not_found_in_known_server_db = 0;
1073 my $not_found_in_foreign_clients_db = 0;
1074 my $local_address;
1075 my $local_mac;
1076 my ($target_ip, $target_port) = split(':', $target);
1078 # Determine the local ip address if target is an ip address
1079 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1080 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1081 } else {
1082 $local_address = $server_address;
1083 }
1085 # Determine the local mac address if target is a mac address
1086 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) {
1087 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1088 my $network_interface= &get_interface_for_ip($loc_ip);
1089 $local_mac = &get_mac_for_interface($network_interface);
1090 } else {
1091 $local_mac = $server_mac_address;
1092 }
1094 # target and source is equal to GOSA -> process here
1095 if (not $done) {
1096 if ($target eq "GOSA" && $source eq "GOSA") {
1097 $done = 1;
1098 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1099 }
1100 }
1102 # target is own address without forward_to_gosa-tag -> process here
1103 if (not $done) {
1104 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1105 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1106 $done = 1;
1107 if ($source eq "GOSA") {
1108 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1109 }
1110 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1111 }
1112 }
1114 # target is a client address in known_clients -> process here
1115 if (not $done) {
1116 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1117 $res = $known_clients_db->select_dbentry($sql);
1118 if (keys(%$res) > 0) {
1119 $done = 1;
1120 my $hostname = $res->{1}->{'hostname'};
1121 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1122 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1123 if ($source eq "GOSA") {
1124 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1125 }
1126 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1128 } else {
1129 $not_found_in_known_clients_db = 1;
1130 }
1131 }
1133 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1134 if (not $done) {
1135 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1136 my $gosa_at;
1137 my $gosa_session_id;
1138 if (($target eq $local_address) && (defined $forward_to_gosa)){
1139 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1140 if ($gosa_at ne $local_address) {
1141 $done = 1;
1142 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1143 }
1144 }
1145 }
1147 # if message should be processed here -> add message to incoming_db
1148 if ($done) {
1149 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1150 # so gosa-si-server knows how to process this kind of messages
1151 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1152 $module = "GosaPackages";
1153 }
1155 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1156 primkey=>[],
1157 headertag=>$header,
1158 targettag=>$target,
1159 xmlmessage=>&encode_base64($msg),
1160 timestamp=>&get_time,
1161 module=>$module,
1162 sessionid=>$session_id,
1163 } );
1165 }
1167 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1168 if (not $done) {
1169 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1170 my $gosa_at;
1171 my $gosa_session_id;
1172 if (($target eq $local_address) && (defined $forward_to_gosa)){
1173 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1174 if ($gosa_at eq $local_address) {
1175 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1176 if( defined $session_reference ) {
1177 $heap = $session_reference->get_heap();
1178 }
1179 if(exists $heap->{'client'}) {
1180 $msg = &encrypt_msg($msg, $GosaPackages_key);
1181 $heap->{'client'}->put($msg);
1182 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1183 }
1184 $done = 1;
1185 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1186 }
1187 }
1189 }
1191 # target is a client address in foreign_clients -> forward to registration server
1192 if (not $done) {
1193 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1194 $res = $foreign_clients_db->select_dbentry($sql);
1195 if (keys(%$res) > 0) {
1196 my $hostname = $res->{1}->{'hostname'};
1197 my ($host_ip, $host_port) = split(/:/, $hostname);
1198 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1199 my $regserver = $res->{1}->{'regserver'};
1200 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1201 my $res = $known_server_db->select_dbentry($sql);
1202 if (keys(%$res) > 0) {
1203 my $regserver_key = $res->{1}->{'hostkey'};
1204 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1205 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1206 if ($source eq "GOSA") {
1207 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1208 }
1209 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1210 }
1211 $done = 1;
1212 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1213 } else {
1214 $not_found_in_foreign_clients_db = 1;
1215 }
1216 }
1218 # target is a server address -> forward to server
1219 if (not $done) {
1220 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1221 $res = $known_server_db->select_dbentry($sql);
1222 if (keys(%$res) > 0) {
1223 my $hostkey = $res->{1}->{'hostkey'};
1225 if ($source eq "GOSA") {
1226 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1227 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1229 }
1231 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1232 $done = 1;
1233 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1234 } else {
1235 $not_found_in_known_server_db = 1;
1236 }
1237 }
1240 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1241 if ( $not_found_in_foreign_clients_db
1242 && $not_found_in_known_server_db
1243 && $not_found_in_known_clients_db) {
1244 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1245 primkey=>[],
1246 headertag=>$header,
1247 targettag=>$target,
1248 xmlmessage=>&encode_base64($msg),
1249 timestamp=>&get_time,
1250 module=>$module,
1251 sessionid=>$session_id,
1252 } );
1253 $done = 1;
1254 &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);
1255 }
1258 if (not $done) {
1259 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1260 if ($source eq "GOSA") {
1261 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1262 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1264 my $session_reference = $kernel->ID_id_to_session($session_id);
1265 if( defined $session_reference ) {
1266 $heap = $session_reference->get_heap();
1267 }
1268 if(exists $heap->{'client'}) {
1269 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1270 $heap->{'client'}->put($error_msg);
1271 }
1272 }
1273 }
1275 }
1277 return;
1278 }
1281 sub next_task {
1282 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1283 my $running_task = POE::Wheel::Run->new(
1284 Program => sub { process_task($session, $heap, $task) },
1285 StdioFilter => POE::Filter::Reference->new(),
1286 StdoutEvent => "task_result",
1287 StderrEvent => "task_debug",
1288 CloseEvent => "task_done",
1289 );
1290 $heap->{task}->{ $running_task->ID } = $running_task;
1291 }
1293 sub handle_task_result {
1294 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1295 my $client_answer = $result->{'answer'};
1296 if( $client_answer =~ s/session_id=(\d+)$// ) {
1297 my $session_id = $1;
1298 if( defined $session_id ) {
1299 my $session_reference = $kernel->ID_id_to_session($session_id);
1300 if( defined $session_reference ) {
1301 $heap = $session_reference->get_heap();
1302 }
1303 }
1305 if(exists $heap->{'client'}) {
1306 $heap->{'client'}->put($client_answer);
1307 }
1308 }
1309 $kernel->sig(CHLD => "child_reap");
1310 }
1312 sub handle_task_debug {
1313 my $result = $_[ARG0];
1314 print STDERR "$result\n";
1315 }
1317 sub handle_task_done {
1318 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1319 delete $heap->{task}->{$task_id};
1320 }
1322 sub process_task {
1323 no strict "refs";
1324 #CHECK: Not @_[...]?
1325 my ($session, $heap, $task) = @_;
1326 my $error = 0;
1327 my $answer_l;
1328 my ($answer_header, @answer_target_l, $answer_source);
1329 my $client_answer = "";
1331 # prepare all variables needed to process message
1332 #my $msg = $task->{'xmlmessage'};
1333 my $msg = &decode_base64($task->{'xmlmessage'});
1334 my $incoming_id = $task->{'id'};
1335 my $module = $task->{'module'};
1336 my $header = $task->{'headertag'};
1337 my $session_id = $task->{'sessionid'};
1338 my $msg_hash;
1339 eval {
1340 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1341 };
1342 daemon_log("ERROR: XML failure '$@'") if ($@);
1343 my $source = @{$msg_hash->{'source'}}[0];
1345 # set timestamp of incoming client uptodate, so client will not
1346 # be deleted from known_clients because of expiration
1347 my $act_time = &get_time();
1348 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1349 my $res = $known_clients_db->exec_statement($sql);
1351 ######################
1352 # process incoming msg
1353 if( $error == 0) {
1354 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1355 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1356 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1358 if ( 0 < @{$answer_l} ) {
1359 my $answer_str = join("\n", @{$answer_l});
1360 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1361 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1362 }
1363 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1364 } else {
1365 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1366 }
1368 }
1369 if( !$answer_l ) { $error++ };
1371 ########
1372 # answer
1373 if( $error == 0 ) {
1375 foreach my $answer ( @{$answer_l} ) {
1376 # check outgoing msg to xml validity
1377 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1378 if( not defined $answer_hash ) { next; }
1380 $answer_header = @{$answer_hash->{'header'}}[0];
1381 @answer_target_l = @{$answer_hash->{'target'}};
1382 $answer_source = @{$answer_hash->{'source'}}[0];
1384 # deliver msg to all targets
1385 foreach my $answer_target ( @answer_target_l ) {
1387 # targets of msg are all gosa-si-clients in known_clients_db
1388 if( $answer_target eq "*" ) {
1389 # answer is for all clients
1390 my $sql_statement= "SELECT * FROM known_clients";
1391 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1392 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1393 my $host_name = $hit->{hostname};
1394 my $host_key = $hit->{hostkey};
1395 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1396 &update_jobdb_status_for_send_msgs($answer, $error);
1397 }
1398 }
1400 # targets of msg are all gosa-si-server in known_server_db
1401 elsif( $answer_target eq "KNOWN_SERVER" ) {
1402 # answer is for all server in known_server
1403 my $sql_statement= "SELECT * FROM $known_server_tn";
1404 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1405 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1406 my $host_name = $hit->{hostname};
1407 my $host_key = $hit->{hostkey};
1408 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1409 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1410 &update_jobdb_status_for_send_msgs($answer, $error);
1411 }
1412 }
1414 # target of msg is GOsa
1415 elsif( $answer_target eq "GOSA" ) {
1416 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1417 my $add_on = "";
1418 if( defined $session_id ) {
1419 $add_on = ".session_id=$session_id";
1420 }
1421 # answer is for GOSA and has to returned to connected client
1422 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1423 $client_answer = $gosa_answer.$add_on;
1424 }
1426 # target of msg is job queue at this host
1427 elsif( $answer_target eq "JOBDB") {
1428 $answer =~ /<header>(\S+)<\/header>/;
1429 my $header;
1430 if( defined $1 ) { $header = $1; }
1431 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1432 &update_jobdb_status_for_send_msgs($answer, $error);
1433 }
1435 # Target of msg is a mac address
1436 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 ) {
1437 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1438 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1439 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1440 my $found_ip_flag = 0;
1441 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1442 my $host_name = $hit->{hostname};
1443 my $host_key = $hit->{hostkey};
1444 $answer =~ s/$answer_target/$host_name/g;
1445 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1446 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1447 &update_jobdb_status_for_send_msgs($answer, $error);
1448 $found_ip_flag++ ;
1449 }
1450 if ($found_ip_flag == 0) {
1451 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1452 my $res = $foreign_clients_db->select_dbentry($sql);
1453 while( my ($hit_num, $hit) = each %{ $res } ) {
1454 my $host_name = $hit->{hostname};
1455 my $reg_server = $hit->{regserver};
1456 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1458 # Fetch key for reg_server
1459 my $reg_server_key;
1460 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1461 my $res = $known_server_db->select_dbentry($sql);
1462 if (exists $res->{1}) {
1463 $reg_server_key = $res->{1}->{'hostkey'};
1464 } else {
1465 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1466 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1467 $reg_server_key = undef;
1468 }
1470 # Send answer to server where client is registered
1471 if (defined $reg_server_key) {
1472 $answer =~ s/$answer_target/$host_name/g;
1473 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1474 &update_jobdb_status_for_send_msgs($answer, $error);
1475 $found_ip_flag++ ;
1476 }
1477 }
1478 }
1479 if( $found_ip_flag == 0) {
1480 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1481 }
1483 # Answer is for one specific host
1484 } else {
1485 # get encrypt_key
1486 my $encrypt_key = &get_encrypt_key($answer_target);
1487 if( not defined $encrypt_key ) {
1488 # unknown target
1489 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1490 next;
1491 }
1492 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1493 &update_jobdb_status_for_send_msgs($answer, $error);
1494 }
1495 }
1496 }
1497 }
1499 my $filter = POE::Filter::Reference->new();
1500 my %result = (
1501 status => "seems ok to me",
1502 answer => $client_answer,
1503 );
1505 my $output = $filter->put( [ \%result ] );
1506 print @$output;
1509 }
1511 sub session_start {
1512 my ($kernel) = $_[KERNEL];
1513 $global_kernel = $kernel;
1514 $kernel->yield('register_at_foreign_servers');
1515 $kernel->yield('create_fai_server_db', $fai_server_tn );
1516 $kernel->yield('create_fai_release_db', $fai_release_tn );
1517 $kernel->yield('watch_for_next_tasks');
1518 $kernel->sig(USR1 => "sig_handler");
1519 $kernel->sig(USR2 => "recreate_packages_db");
1520 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1521 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1522 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1523 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1524 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1525 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1526 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1528 # Start opsi check
1529 if ($opsi_enabled eq "true") {
1530 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1531 }
1533 }
1536 sub watch_for_done_jobs {
1537 #CHECK: $heap for what?
1538 my ($kernel,$heap) = @_[KERNEL, HEAP];
1540 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1541 my $res = $job_db->select_dbentry( $sql_statement );
1543 while( my ($id, $hit) = each %{$res} ) {
1544 my $jobdb_id = $hit->{id};
1545 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1546 my $res = $job_db->del_dbentry($sql_statement);
1547 }
1549 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1550 }
1553 sub watch_for_opsi_jobs {
1554 my ($kernel) = $_[KERNEL];
1556 # 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
1557 # opsi install job is to parse the xml message. There is still the correct header.
1558 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1559 my $res = $job_db->select_dbentry( $sql_statement );
1561 # Ask OPSI for an update of the running jobs
1562 while (my ($id, $hit) = each %$res ) {
1563 # Determine current parameters of the job
1564 my $hostId = $hit->{'plainname'};
1565 my $macaddress = $hit->{'macaddress'};
1566 my $progress = $hit->{'progress'};
1568 my $result= {};
1570 # For hosts, only return the products that are or get installed
1571 my $callobj;
1572 $callobj = {
1573 method => 'getProductStates_hash',
1574 params => [ $hostId ],
1575 id => 1,
1576 };
1578 my $hres = $opsi_client->call($opsi_url, $callobj);
1579 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1580 if (not &check_opsi_res($hres)) {
1581 my $htmp= $hres->result->{$hostId};
1583 # Check state != not_installed or action == setup -> load and add
1584 my $products= 0;
1585 my $installed= 0;
1586 my $installing = 0;
1587 my $error= 0;
1588 my @installed_list;
1589 my @error_list;
1590 my $act_status = "none";
1591 foreach my $product (@{$htmp}){
1593 if ($product->{'installationStatus'} ne "not_installed" or
1594 $product->{'actionRequest'} eq "setup"){
1596 # Increase number of products for this host
1597 $products++;
1599 if ($product->{'installationStatus'} eq "failed"){
1600 $result->{$product->{'productId'}}= "error";
1601 unshift(@error_list, $product->{'productId'});
1602 $error++;
1603 }
1604 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1605 $result->{$product->{'productId'}}= "installed";
1606 unshift(@installed_list, $product->{'productId'});
1607 $installed++;
1608 }
1609 if ($product->{'installationStatus'} eq "installing"){
1610 $result->{$product->{'productId'}}= "installing";
1611 $installing++;
1612 $act_status = "installing - ".$product->{'productId'};
1613 }
1614 }
1615 }
1617 # Estimate "rough" progress, avoid division by zero
1618 if ($products == 0) {
1619 $result->{'progress'}= 0;
1620 } else {
1621 $result->{'progress'}= int($installed * 100 / $products);
1622 }
1624 # Set updates in job queue
1625 if ((not $error) && (not $installing) && ($installed)) {
1626 $act_status = "installed - ".join(", ", @installed_list);
1627 }
1628 if ($error) {
1629 $act_status = "error - ".join(", ", @error_list);
1630 }
1631 if ($progress ne $result->{'progress'} ) {
1632 # Updating progress and result
1633 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1634 my $update_res = $job_db->update_dbentry($update_statement);
1635 }
1636 if ($progress eq 100) {
1637 # Updateing status
1638 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1639 if ($error) {
1640 $done_statement .= "status='error'";
1641 } else {
1642 $done_statement .= "status='done'";
1643 }
1644 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1645 my $done_res = $job_db->update_dbentry($done_statement);
1646 }
1649 }
1650 }
1652 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1653 }
1656 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1657 sub watch_for_modified_jobs {
1658 my ($kernel,$heap) = @_[KERNEL, HEAP];
1660 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1661 my $res = $job_db->select_dbentry( $sql_statement );
1663 # if db contains no jobs which should be update, do nothing
1664 if (keys %$res != 0) {
1666 if ($job_synchronization eq "true") {
1667 # make out of the db result a gosa-si message
1668 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1670 # update all other SI-server
1671 &inform_all_other_si_server($update_msg);
1672 }
1674 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1675 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1676 $res = $job_db->update_dbentry($sql_statement);
1677 }
1679 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1680 }
1683 sub watch_for_new_jobs {
1684 if($watch_for_new_jobs_in_progress == 0) {
1685 $watch_for_new_jobs_in_progress = 1;
1686 my ($kernel,$heap) = @_[KERNEL, HEAP];
1688 # check gosa job quaeue for jobs with executable timestamp
1689 my $timestamp = &get_time();
1690 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1691 my $res = $job_db->exec_statement( $sql_statement );
1693 # Merge all new jobs that would do the same actions
1694 my @drops;
1695 my $hits;
1696 foreach my $hit (reverse @{$res} ) {
1697 my $macaddress= lc @{$hit}[8];
1698 my $headertag= @{$hit}[5];
1699 if(
1700 defined($hits->{$macaddress}) &&
1701 defined($hits->{$macaddress}->{$headertag}) &&
1702 defined($hits->{$macaddress}->{$headertag}[0])
1703 ) {
1704 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1705 }
1706 $hits->{$macaddress}->{$headertag}= $hit;
1707 }
1709 # Delete new jobs with a matching job in state 'processing'
1710 foreach my $macaddress (keys %{$hits}) {
1711 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1712 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1713 if(defined($jobdb_id)) {
1714 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1715 my $res = $job_db->exec_statement( $sql_statement );
1716 foreach my $hit (@{$res}) {
1717 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1718 }
1719 } else {
1720 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1721 }
1722 }
1723 }
1725 # Commit deletion
1726 $job_db->exec_statementlist(\@drops);
1728 # Look for new jobs that could be executed
1729 foreach my $macaddress (keys %{$hits}) {
1731 # Look if there is an executing job
1732 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1733 my $res = $job_db->exec_statement( $sql_statement );
1735 # Skip new jobs for host if there is a processing job
1736 if(defined($res) and defined @{$res}[0]) {
1737 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1738 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1739 if(@{$row}[5] eq 'trigger_action_reinstall') {
1740 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1741 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1742 if(defined($res_2) and defined @{$res_2}[0]) {
1743 # Set status from goto-activation to 'waiting' and update timestamp
1744 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1745 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1746 }
1747 }
1748 next;
1749 }
1751 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1752 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1753 if(defined($jobdb_id)) {
1754 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1756 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1757 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1758 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1760 # expect macaddress is unique!!!!!!
1761 my $target = $res_hash->{1}->{hostname};
1763 # change header
1764 $job_msg =~ s/<header>job_/<header>gosa_/;
1766 # add sqlite_id
1767 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1769 $job_msg =~ /<header>(\S+)<\/header>/;
1770 my $header = $1 ;
1771 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1773 # update status in job queue to 'processing'
1774 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1775 my $res = $job_db->update_dbentry($sql_statement);
1776 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1778 # We don't want parallel processing
1779 last;
1780 }
1781 }
1782 }
1784 $watch_for_new_jobs_in_progress = 0;
1785 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1786 }
1787 }
1790 sub watch_for_new_messages {
1791 my ($kernel,$heap) = @_[KERNEL, HEAP];
1792 my @coll_user_msg; # collection list of outgoing messages
1794 # check messaging_db for new incoming messages with executable timestamp
1795 my $timestamp = &get_time();
1796 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1797 my $res = $messaging_db->exec_statement( $sql_statement );
1798 foreach my $hit (@{$res}) {
1800 # create outgoing messages
1801 my $message_to = @{$hit}[3];
1802 # translate message_to to plain login name
1803 my @message_to_l = split(/,/, $message_to);
1804 my %receiver_h;
1805 foreach my $receiver (@message_to_l) {
1806 if ($receiver =~ /^u_([\s\S]*)$/) {
1807 $receiver_h{$1} = 0;
1808 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1809 my $group_name = $1;
1810 # fetch all group members from ldap and add them to receiver hash
1811 my $ldap_handle = &get_ldap_handle();
1812 if (defined $ldap_handle) {
1813 my $mesg = $ldap_handle->search(
1814 base => $ldap_base,
1815 scope => 'sub',
1816 attrs => ['memberUid'],
1817 filter => "cn=$group_name",
1818 );
1819 if ($mesg->count) {
1820 my @entries = $mesg->entries;
1821 foreach my $entry (@entries) {
1822 my @receivers= $entry->get_value("memberUid");
1823 foreach my $receiver (@receivers) {
1824 $receiver_h{$1} = 0;
1825 }
1826 }
1827 }
1828 # translating errors ?
1829 if ($mesg->code) {
1830 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1831 }
1832 # ldap handle error ?
1833 } else {
1834 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1835 }
1836 } else {
1837 my $sbjct = &encode_base64(@{$hit}[1]);
1838 my $msg = &encode_base64(@{$hit}[7]);
1839 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1840 }
1841 }
1842 my @receiver_l = keys(%receiver_h);
1844 my $message_id = @{$hit}[0];
1846 #add each outgoing msg to messaging_db
1847 my $receiver;
1848 foreach $receiver (@receiver_l) {
1849 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1850 "VALUES ('".
1851 $message_id."', '". # id
1852 @{$hit}[1]."', '". # subject
1853 @{$hit}[2]."', '". # message_from
1854 $receiver."', '". # message_to
1855 "none"."', '". # flag
1856 "out"."', '". # direction
1857 @{$hit}[6]."', '". # delivery_time
1858 @{$hit}[7]."', '". # message
1859 $timestamp."'". # timestamp
1860 ")";
1861 &daemon_log("M DEBUG: $sql_statement", 1);
1862 my $res = $messaging_db->exec_statement($sql_statement);
1863 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1864 }
1866 # set incoming message to flag d=deliverd
1867 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1868 &daemon_log("M DEBUG: $sql_statement", 7);
1869 $res = $messaging_db->update_dbentry($sql_statement);
1870 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1871 }
1873 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1874 return;
1875 }
1877 sub watch_for_delivery_messages {
1878 my ($kernel, $heap) = @_[KERNEL, HEAP];
1880 # select outgoing messages
1881 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1882 #&daemon_log("0 DEBUG: $sql", 7);
1883 my $res = $messaging_db->exec_statement( $sql_statement );
1885 # build out msg for each usr
1886 foreach my $hit (@{$res}) {
1887 my $receiver = @{$hit}[3];
1888 my $msg_id = @{$hit}[0];
1889 my $subject = @{$hit}[1];
1890 my $message = @{$hit}[7];
1892 # resolve usr -> host where usr is logged in
1893 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1894 #&daemon_log("0 DEBUG: $sql", 7);
1895 my $res = $login_users_db->exec_statement($sql);
1897 # reciver is logged in nowhere
1898 if (not ref(@$res[0]) eq "ARRAY") { next; }
1900 my $send_succeed = 0;
1901 foreach my $hit (@$res) {
1902 my $receiver_host = @$hit[0];
1903 my $delivered2host = 0;
1904 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1906 # Looking for host in know_clients_db
1907 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1908 my $res = $known_clients_db->exec_statement($sql);
1910 # Host is known in known_clients_db
1911 if (ref(@$res[0]) eq "ARRAY") {
1912 my $receiver_key = @{@{$res}[0]}[2];
1913 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1914 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1915 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1916 if ($error == 0 ) {
1917 $send_succeed++ ;
1918 $delivered2host++ ;
1919 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1920 } else {
1921 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1922 }
1923 }
1925 # Message already send, do not need to do anything more, otherwise ...
1926 if ($delivered2host) { next;}
1928 # ...looking for host in foreign_clients_db
1929 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1930 $res = $foreign_clients_db->exec_statement($sql);
1932 # Host is known in foreign_clients_db
1933 if (ref(@$res[0]) eq "ARRAY") {
1934 my $registration_server = @{@{$res}[0]}[2];
1936 # Fetch encryption key for registration server
1937 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1938 my $res = $known_server_db->exec_statement($sql);
1939 if (ref(@$res[0]) eq "ARRAY") {
1940 my $registration_server_key = @{@{$res}[0]}[3];
1941 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1942 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1943 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1944 if ($error == 0 ) {
1945 $send_succeed++ ;
1946 $delivered2host++ ;
1947 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1948 } else {
1949 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1950 }
1952 } else {
1953 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1954 "registrated at server '$registration_server', ".
1955 "but no data available in known_server_db ", 1);
1956 }
1957 }
1959 if (not $delivered2host) {
1960 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1961 }
1962 }
1964 if ($send_succeed) {
1965 # set outgoing msg at db to deliverd
1966 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1967 my $res = $messaging_db->exec_statement($sql);
1968 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1969 } else {
1970 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
1971 }
1972 }
1974 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1975 return;
1976 }
1979 sub watch_for_done_messages {
1980 my ($kernel,$heap) = @_[KERNEL, HEAP];
1982 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1983 #&daemon_log("0 DEBUG: $sql", 7);
1984 my $res = $messaging_db->exec_statement($sql);
1986 foreach my $hit (@{$res}) {
1987 my $msg_id = @{$hit}[0];
1989 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1990 #&daemon_log("0 DEBUG: $sql", 7);
1991 my $res = $messaging_db->exec_statement($sql);
1993 # not all usr msgs have been seen till now
1994 if ( ref(@$res[0]) eq "ARRAY") { next; }
1996 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1997 #&daemon_log("0 DEBUG: $sql", 7);
1998 $res = $messaging_db->exec_statement($sql);
2000 }
2002 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2003 return;
2004 }
2007 sub watch_for_old_known_clients {
2008 my ($kernel,$heap) = @_[KERNEL, HEAP];
2010 my $sql_statement = "SELECT * FROM $known_clients_tn";
2011 my $res = $known_clients_db->select_dbentry( $sql_statement );
2013 my $act_time = int(&get_time());
2015 while ( my ($hit_num, $hit) = each %$res) {
2016 my $expired_timestamp = int($hit->{'timestamp'});
2017 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2018 my $dt = DateTime->new( year => $1,
2019 month => $2,
2020 day => $3,
2021 hour => $4,
2022 minute => $5,
2023 second => $6,
2024 );
2026 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2027 $expired_timestamp = $dt->ymd('').$dt->hms('');
2028 if ($act_time > $expired_timestamp) {
2029 my $hostname = $hit->{'hostname'};
2030 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2031 my $del_res = $known_clients_db->exec_statement($del_sql);
2033 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2034 }
2036 }
2038 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2039 }
2042 sub watch_for_next_tasks {
2043 my ($kernel,$heap) = @_[KERNEL, HEAP];
2045 my $sql = "SELECT * FROM $incoming_tn";
2046 my $res = $incoming_db->select_dbentry($sql);
2048 while ( my ($hit_num, $hit) = each %$res) {
2049 my $headertag = $hit->{'headertag'};
2050 if ($headertag =~ /^answer_(\d+)/) {
2051 # do not start processing, this message is for a still running POE::Wheel
2052 next;
2053 }
2054 my $message_id = $hit->{'id'};
2055 my $session_id = $hit->{'sessionid'};
2056 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2057 $kernel->yield('next_task', $hit);
2059 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2060 my $res = $incoming_db->exec_statement($sql);
2061 }
2063 $kernel->delay_set('watch_for_next_tasks', 1);
2064 }
2067 sub get_ldap_handle {
2068 my ($session_id) = @_;
2069 my $heap;
2070 my $ldap_handle;
2072 if (not defined $session_id ) { $session_id = 0 };
2073 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2075 if ($session_id == 0) {
2076 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2077 $ldap_handle = Net::LDAP->new( $ldap_uri );
2078 if (defined $ldap_handle) {
2079 $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!");
2080 } else {
2081 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2082 }
2084 } else {
2085 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2086 if( defined $session_reference ) {
2087 $heap = $session_reference->get_heap();
2088 }
2090 if (not defined $heap) {
2091 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2092 return;
2093 }
2095 # TODO: This "if" is nonsense, because it doesn't prove that the
2096 # used handle is still valid - or if we've to reconnect...
2097 #if (not exists $heap->{ldap_handle}) {
2098 $ldap_handle = Net::LDAP->new( $ldap_uri );
2099 $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!");
2100 $heap->{ldap_handle} = $ldap_handle;
2101 #}
2102 }
2103 return $ldap_handle;
2104 }
2107 sub change_fai_state {
2108 my ($st, $targets, $session_id) = @_;
2109 $session_id = 0 if not defined $session_id;
2110 # Set FAI state to localboot
2111 my %mapActions= (
2112 reboot => '',
2113 update => 'softupdate',
2114 localboot => 'localboot',
2115 reinstall => 'install',
2116 rescan => '',
2117 wake => '',
2118 memcheck => 'memcheck',
2119 sysinfo => 'sysinfo',
2120 install => 'install',
2121 );
2123 # Return if this is unknown
2124 if (!exists $mapActions{ $st }){
2125 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2126 return;
2127 }
2129 my $state= $mapActions{ $st };
2131 my $ldap_handle = &get_ldap_handle($session_id);
2132 if( defined($ldap_handle) ) {
2134 # Build search filter for hosts
2135 my $search= "(&(objectClass=GOhard)";
2136 foreach (@{$targets}){
2137 $search.= "(macAddress=$_)";
2138 }
2139 $search.= ")";
2141 # If there's any host inside of the search string, procress them
2142 if (!($search =~ /macAddress/)){
2143 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2144 return;
2145 }
2147 # Perform search for Unit Tag
2148 my $mesg = $ldap_handle->search(
2149 base => $ldap_base,
2150 scope => 'sub',
2151 attrs => ['dn', 'FAIstate', 'objectClass'],
2152 filter => "$search"
2153 );
2155 if ($mesg->count) {
2156 my @entries = $mesg->entries;
2157 if (0 == @entries) {
2158 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2159 }
2161 foreach my $entry (@entries) {
2162 # Only modify entry if it is not set to '$state'
2163 if ($entry->get_value("FAIstate") ne "$state"){
2164 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2165 my $result;
2166 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2167 if (exists $tmp{'FAIobject'}){
2168 if ($state eq ''){
2169 $result= $ldap_handle->modify($entry->dn, changes => [
2170 delete => [ FAIstate => [] ] ]);
2171 } else {
2172 $result= $ldap_handle->modify($entry->dn, changes => [
2173 replace => [ FAIstate => $state ] ]);
2174 }
2175 } elsif ($state ne ''){
2176 $result= $ldap_handle->modify($entry->dn, changes => [
2177 add => [ objectClass => 'FAIobject' ],
2178 add => [ FAIstate => $state ] ]);
2179 }
2181 # Errors?
2182 if ($result->code){
2183 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2184 }
2185 } else {
2186 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2187 }
2188 }
2189 } else {
2190 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2191 }
2193 # if no ldap handle defined
2194 } else {
2195 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2196 }
2198 return;
2199 }
2202 sub change_goto_state {
2203 my ($st, $targets, $session_id) = @_;
2204 $session_id = 0 if not defined $session_id;
2206 # Switch on or off?
2207 my $state= $st eq 'active' ? 'active': 'locked';
2209 my $ldap_handle = &get_ldap_handle($session_id);
2210 if( defined($ldap_handle) ) {
2212 # Build search filter for hosts
2213 my $search= "(&(objectClass=GOhard)";
2214 foreach (@{$targets}){
2215 $search.= "(macAddress=$_)";
2216 }
2217 $search.= ")";
2219 # If there's any host inside of the search string, procress them
2220 if (!($search =~ /macAddress/)){
2221 return;
2222 }
2224 # Perform search for Unit Tag
2225 my $mesg = $ldap_handle->search(
2226 base => $ldap_base,
2227 scope => 'sub',
2228 attrs => ['dn', 'gotoMode'],
2229 filter => "$search"
2230 );
2232 if ($mesg->count) {
2233 my @entries = $mesg->entries;
2234 foreach my $entry (@entries) {
2236 # Only modify entry if it is not set to '$state'
2237 if ($entry->get_value("gotoMode") ne $state){
2239 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2240 my $result;
2241 $result= $ldap_handle->modify($entry->dn, changes => [
2242 replace => [ gotoMode => $state ] ]);
2244 # Errors?
2245 if ($result->code){
2246 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2247 }
2249 }
2250 }
2251 } else {
2252 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2253 }
2255 }
2256 }
2259 sub run_recreate_packages_db {
2260 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2261 my $session_id = $session->ID;
2262 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2263 $kernel->yield('create_fai_release_db', $fai_release_tn);
2264 $kernel->yield('create_fai_server_db', $fai_server_tn);
2265 return;
2266 }
2269 sub run_create_fai_server_db {
2270 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2271 my $session_id = $session->ID;
2272 my $task = POE::Wheel::Run->new(
2273 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2274 StdoutEvent => "session_run_result",
2275 StderrEvent => "session_run_debug",
2276 CloseEvent => "session_run_done",
2277 );
2279 $heap->{task}->{ $task->ID } = $task;
2280 return;
2281 }
2284 sub create_fai_server_db {
2285 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2286 my $result;
2288 if (not defined $session_id) { $session_id = 0; }
2289 my $ldap_handle = &get_ldap_handle();
2290 if(defined($ldap_handle)) {
2291 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2292 my $mesg= $ldap_handle->search(
2293 base => $ldap_base,
2294 scope => 'sub',
2295 attrs => ['FAIrepository', 'gosaUnitTag'],
2296 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2297 );
2298 if($mesg->{'resultCode'} == 0 &&
2299 $mesg->count != 0) {
2300 foreach my $entry (@{$mesg->{entries}}) {
2301 if($entry->exists('FAIrepository')) {
2302 # Add an entry for each Repository configured for server
2303 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2304 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2305 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2306 $result= $fai_server_db->add_dbentry( {
2307 table => $table_name,
2308 primkey => ['server', 'fai_release', 'tag'],
2309 server => $tmp_url,
2310 fai_release => $tmp_release,
2311 sections => $tmp_sections,
2312 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2313 } );
2314 }
2315 }
2316 }
2317 }
2318 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2320 # TODO: Find a way to post the 'create_packages_list_db' event
2321 if(not defined($dont_create_packages_list)) {
2322 &create_packages_list_db(undef, undef, $session_id);
2323 }
2324 }
2326 $ldap_handle->disconnect;
2327 return $result;
2328 }
2331 sub run_create_fai_release_db {
2332 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2333 my $session_id = $session->ID;
2334 my $task = POE::Wheel::Run->new(
2335 Program => sub { &create_fai_release_db($table_name, $session_id) },
2336 StdoutEvent => "session_run_result",
2337 StderrEvent => "session_run_debug",
2338 CloseEvent => "session_run_done",
2339 );
2341 $heap->{task}->{ $task->ID } = $task;
2342 return;
2343 }
2346 sub create_fai_release_db {
2347 my ($table_name, $session_id) = @_;
2348 my $result;
2350 # used for logging
2351 if (not defined $session_id) { $session_id = 0; }
2353 my $ldap_handle = &get_ldap_handle();
2354 if(defined($ldap_handle)) {
2355 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2356 my $mesg= $ldap_handle->search(
2357 base => $ldap_base,
2358 scope => 'sub',
2359 attrs => [],
2360 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2361 );
2362 if($mesg->{'resultCode'} == 0 &&
2363 $mesg->count != 0) {
2364 # Walk through all possible FAI container ou's
2365 my @sql_list;
2366 my $timestamp= &get_time();
2367 foreach my $ou (@{$mesg->{entries}}) {
2368 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2369 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2370 my @tmp_array=get_fai_release_entries($tmp_classes);
2371 if(@tmp_array) {
2372 foreach my $entry (@tmp_array) {
2373 if(defined($entry) && ref($entry) eq 'HASH') {
2374 my $sql=
2375 "INSERT INTO $table_name "
2376 ."(timestamp, fai_release, class, type, state) VALUES ("
2377 .$timestamp.","
2378 ."'".$entry->{'release'}."',"
2379 ."'".$entry->{'class'}."',"
2380 ."'".$entry->{'type'}."',"
2381 ."'".$entry->{'state'}."')";
2382 push @sql_list, $sql;
2383 }
2384 }
2385 }
2386 }
2387 }
2389 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2390 if(@sql_list) {
2391 unshift @sql_list, "DELETE FROM $table_name";
2392 $fai_release_db->exec_statementlist(\@sql_list);
2393 }
2394 daemon_log("$session_id DEBUG: Done with inserting",7);
2395 }
2396 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2397 }
2398 $ldap_handle->disconnect;
2399 return $result;
2400 }
2402 sub get_fai_types {
2403 my $tmp_classes = shift || return undef;
2404 my @result;
2406 foreach my $type(keys %{$tmp_classes}) {
2407 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2408 my $entry = {
2409 type => $type,
2410 state => $tmp_classes->{$type}[0],
2411 };
2412 push @result, $entry;
2413 }
2414 }
2416 return @result;
2417 }
2419 sub get_fai_state {
2420 my $result = "";
2421 my $tmp_classes = shift || return $result;
2423 foreach my $type(keys %{$tmp_classes}) {
2424 if(defined($tmp_classes->{$type}[0])) {
2425 $result = $tmp_classes->{$type}[0];
2427 # State is equal for all types in class
2428 last;
2429 }
2430 }
2432 return $result;
2433 }
2435 sub resolve_fai_classes {
2436 my ($fai_base, $ldap_handle, $session_id) = @_;
2437 if (not defined $session_id) { $session_id = 0; }
2438 my $result;
2439 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2440 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2441 my $fai_classes;
2443 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2444 my $mesg= $ldap_handle->search(
2445 base => $fai_base,
2446 scope => 'sub',
2447 attrs => ['cn','objectClass','FAIstate'],
2448 filter => $fai_filter,
2449 );
2450 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2452 if($mesg->{'resultCode'} == 0 &&
2453 $mesg->count != 0) {
2454 foreach my $entry (@{$mesg->{entries}}) {
2455 if($entry->exists('cn')) {
2456 my $tmp_dn= $entry->dn();
2458 # Skip classname and ou dn parts for class
2459 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2461 # Skip classes without releases
2462 if((!defined($tmp_release)) || length($tmp_release)==0) {
2463 next;
2464 }
2466 my $tmp_cn= $entry->get_value('cn');
2467 my $tmp_state= $entry->get_value('FAIstate');
2469 my $tmp_type;
2470 # Get FAI type
2471 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2472 if(grep $_ eq $oclass, @possible_fai_classes) {
2473 $tmp_type= $oclass;
2474 last;
2475 }
2476 }
2478 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2479 # A Subrelease
2480 my @sub_releases = split(/,/, $tmp_release);
2482 # Walk through subreleases and build hash tree
2483 my $hash;
2484 while(my $tmp_sub_release = pop @sub_releases) {
2485 $hash .= "\{'$tmp_sub_release'\}->";
2486 }
2487 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2488 } else {
2489 # A branch, no subrelease
2490 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2491 }
2492 } elsif (!$entry->exists('cn')) {
2493 my $tmp_dn= $entry->dn();
2494 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2496 # Skip classes without releases
2497 if((!defined($tmp_release)) || length($tmp_release)==0) {
2498 next;
2499 }
2501 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2502 # A Subrelease
2503 my @sub_releases= split(/,/, $tmp_release);
2505 # Walk through subreleases and build hash tree
2506 my $hash;
2507 while(my $tmp_sub_release = pop @sub_releases) {
2508 $hash .= "\{'$tmp_sub_release'\}->";
2509 }
2510 # Remove the last two characters
2511 chop($hash);
2512 chop($hash);
2514 eval('$fai_classes->'.$hash.'= {}');
2515 } else {
2516 # A branch, no subrelease
2517 if(!exists($fai_classes->{$tmp_release})) {
2518 $fai_classes->{$tmp_release} = {};
2519 }
2520 }
2521 }
2522 }
2524 # The hash is complete, now we can honor the copy-on-write based missing entries
2525 foreach my $release (keys %$fai_classes) {
2526 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2527 }
2528 }
2529 return $result;
2530 }
2532 sub apply_fai_inheritance {
2533 my $fai_classes = shift || return {};
2534 my $tmp_classes;
2536 # Get the classes from the branch
2537 foreach my $class (keys %{$fai_classes}) {
2538 # Skip subreleases
2539 if($class =~ /^ou=.*$/) {
2540 next;
2541 } else {
2542 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2543 }
2544 }
2546 # Apply to each subrelease
2547 foreach my $subrelease (keys %{$fai_classes}) {
2548 if($subrelease =~ /ou=/) {
2549 foreach my $tmp_class (keys %{$tmp_classes}) {
2550 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2551 $fai_classes->{$subrelease}->{$tmp_class} =
2552 deep_copy($tmp_classes->{$tmp_class});
2553 } else {
2554 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2555 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2556 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2557 deep_copy($tmp_classes->{$tmp_class}->{$type});
2558 }
2559 }
2560 }
2561 }
2562 }
2563 }
2565 # Find subreleases in deeper levels
2566 foreach my $subrelease (keys %{$fai_classes}) {
2567 if($subrelease =~ /ou=/) {
2568 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2569 if($subsubrelease =~ /ou=/) {
2570 apply_fai_inheritance($fai_classes->{$subrelease});
2571 }
2572 }
2573 }
2574 }
2576 return $fai_classes;
2577 }
2579 sub get_fai_release_entries {
2580 my $tmp_classes = shift || return;
2581 my $parent = shift || "";
2582 my @result = shift || ();
2584 foreach my $entry (keys %{$tmp_classes}) {
2585 if(defined($entry)) {
2586 if($entry =~ /^ou=.*$/) {
2587 my $release_name = $entry;
2588 $release_name =~ s/ou=//g;
2589 if(length($parent)>0) {
2590 $release_name = $parent."/".$release_name;
2591 }
2592 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2593 foreach my $bufentry(@bufentries) {
2594 push @result, $bufentry;
2595 }
2596 } else {
2597 my @types = get_fai_types($tmp_classes->{$entry});
2598 foreach my $type (@types) {
2599 push @result,
2600 {
2601 'class' => $entry,
2602 'type' => $type->{'type'},
2603 'release' => $parent,
2604 'state' => $type->{'state'},
2605 };
2606 }
2607 }
2608 }
2609 }
2611 return @result;
2612 }
2614 sub deep_copy {
2615 my $this = shift;
2616 if (not ref $this) {
2617 $this;
2618 } elsif (ref $this eq "ARRAY") {
2619 [map deep_copy($_), @$this];
2620 } elsif (ref $this eq "HASH") {
2621 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2622 } else { die "what type is $_?" }
2623 }
2626 sub session_run_result {
2627 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2628 $kernel->sig(CHLD => "child_reap");
2629 }
2631 sub session_run_debug {
2632 my $result = $_[ARG0];
2633 print STDERR "$result\n";
2634 }
2636 sub session_run_done {
2637 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2638 delete $heap->{task}->{$task_id};
2639 }
2642 sub create_sources_list {
2643 my $session_id = shift;
2644 my $ldap_handle = &main::get_ldap_handle;
2645 my $result="/tmp/gosa_si_tmp_sources_list";
2647 # Remove old file
2648 if(stat($result)) {
2649 unlink($result);
2650 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2651 }
2653 my $fh;
2654 open($fh, ">$result");
2655 if (not defined $fh) {
2656 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2657 return undef;
2658 }
2659 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2660 my $mesg=$ldap_handle->search(
2661 base => $main::ldap_server_dn,
2662 scope => 'base',
2663 attrs => 'FAIrepository',
2664 filter => 'objectClass=FAIrepositoryServer'
2665 );
2666 if($mesg->count) {
2667 foreach my $entry(@{$mesg->{'entries'}}) {
2668 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2669 my ($server, $tag, $release, $sections)= split /\|/, $value;
2670 my $line = "deb $server $release";
2671 $sections =~ s/,/ /g;
2672 $line.= " $sections";
2673 print $fh $line."\n";
2674 }
2675 }
2676 }
2677 } else {
2678 if (defined $main::ldap_server_dn){
2679 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2680 } else {
2681 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2682 }
2683 }
2684 close($fh);
2686 return $result;
2687 }
2690 sub run_create_packages_list_db {
2691 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2692 my $session_id = $session->ID;
2694 my $task = POE::Wheel::Run->new(
2695 Priority => +20,
2696 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2697 StdoutEvent => "session_run_result",
2698 StderrEvent => "session_run_debug",
2699 CloseEvent => "session_run_done",
2700 );
2701 $heap->{task}->{ $task->ID } = $task;
2702 }
2705 sub create_packages_list_db {
2706 my ($ldap_handle, $sources_file, $session_id) = @_;
2708 # it should not be possible to trigger a recreation of packages_list_db
2709 # while packages_list_db is under construction, so set flag packages_list_under_construction
2710 # which is tested befor recreation can be started
2711 if (-r $packages_list_under_construction) {
2712 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2713 return;
2714 } else {
2715 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2716 # set packages_list_under_construction to true
2717 system("touch $packages_list_under_construction");
2718 @packages_list_statements=();
2719 }
2721 if (not defined $session_id) { $session_id = 0; }
2722 if (not defined $ldap_handle) {
2723 $ldap_handle= &get_ldap_handle();
2725 if (not defined $ldap_handle) {
2726 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2727 unlink($packages_list_under_construction);
2728 return;
2729 }
2730 }
2731 if (not defined $sources_file) {
2732 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2733 $sources_file = &create_sources_list($session_id);
2734 }
2736 if (not defined $sources_file) {
2737 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2738 unlink($packages_list_under_construction);
2739 return;
2740 }
2742 my $line;
2744 open(CONFIG, "<$sources_file") or do {
2745 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2746 unlink($packages_list_under_construction);
2747 return;
2748 };
2750 # Read lines
2751 while ($line = <CONFIG>){
2752 # Unify
2753 chop($line);
2754 $line =~ s/^\s+//;
2755 $line =~ s/^\s+/ /;
2757 # Strip comments
2758 $line =~ s/#.*$//g;
2760 # Skip empty lines
2761 if ($line =~ /^\s*$/){
2762 next;
2763 }
2765 # Interpret deb line
2766 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2767 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2768 my $section;
2769 foreach $section (split(' ', $sections)){
2770 &parse_package_info( $baseurl, $dist, $section, $session_id );
2771 }
2772 }
2773 }
2775 close (CONFIG);
2778 if(keys(%repo_dirs)) {
2779 find(\&cleanup_and_extract, keys( %repo_dirs ));
2780 &main::strip_packages_list_statements();
2781 $packages_list_db->exec_statementlist(\@packages_list_statements);
2782 }
2783 unlink($packages_list_under_construction);
2784 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2785 return;
2786 }
2788 # This function should do some intensive task to minimize the db-traffic
2789 sub strip_packages_list_statements {
2790 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2791 my @new_statement_list=();
2792 my $hash;
2793 my $insert_hash;
2794 my $update_hash;
2795 my $delete_hash;
2796 my $local_timestamp=get_time();
2798 foreach my $existing_entry (@existing_entries) {
2799 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2800 }
2802 foreach my $statement (@packages_list_statements) {
2803 if($statement =~ /^INSERT/i) {
2804 # Assign the values from the insert statement
2805 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2806 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2807 if(exists($hash->{$distribution}->{$package}->{$version})) {
2808 # If section or description has changed, update the DB
2809 if(
2810 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2811 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2812 ) {
2813 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2814 }
2815 } else {
2816 # Insert a non-existing entry to db
2817 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2818 }
2819 } elsif ($statement =~ /^UPDATE/i) {
2820 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2821 /^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;
2822 foreach my $distribution (keys %{$hash}) {
2823 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2824 # update the insertion hash to execute only one query per package (insert instead insert+update)
2825 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2826 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2827 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2828 my $section;
2829 my $description;
2830 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2831 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2832 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2833 }
2834 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2835 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2836 }
2837 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2838 }
2839 }
2840 }
2841 }
2842 }
2844 # TODO: Check for orphaned entries
2846 # unroll the insert_hash
2847 foreach my $distribution (keys %{$insert_hash}) {
2848 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2849 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2850 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2851 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2852 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2853 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2854 ."'$local_timestamp')";
2855 }
2856 }
2857 }
2859 # unroll the update hash
2860 foreach my $distribution (keys %{$update_hash}) {
2861 foreach my $package (keys %{$update_hash->{$distribution}}) {
2862 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2863 my $set = "";
2864 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2865 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2866 }
2867 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2868 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2869 }
2870 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2871 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2872 }
2873 if(defined($set) and length($set) > 0) {
2874 $set .= "timestamp = '$local_timestamp'";
2875 } else {
2876 next;
2877 }
2878 push @new_statement_list,
2879 "UPDATE $main::packages_list_tn SET $set WHERE"
2880 ." distribution = '$distribution'"
2881 ." AND package = '$package'"
2882 ." AND version = '$version'";
2883 }
2884 }
2885 }
2887 @packages_list_statements = @new_statement_list;
2888 }
2891 sub parse_package_info {
2892 my ($baseurl, $dist, $section, $session_id)= @_;
2893 my ($package);
2894 if (not defined $session_id) { $session_id = 0; }
2895 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2896 $repo_dirs{ "${repo_path}/pool" } = 1;
2898 foreach $package ("Packages.gz"){
2899 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2900 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2901 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2902 }
2904 }
2907 sub get_package {
2908 my ($url, $dest, $session_id)= @_;
2909 if (not defined $session_id) { $session_id = 0; }
2911 my $tpath = dirname($dest);
2912 -d "$tpath" || mkpath "$tpath";
2914 # This is ugly, but I've no time to take a look at "how it works in perl"
2915 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2916 system("gunzip -cd '$dest' > '$dest.in'");
2917 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2918 unlink($dest);
2919 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2920 } else {
2921 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2922 }
2923 return 0;
2924 }
2927 sub parse_package {
2928 my ($path, $dist, $srv_path, $session_id)= @_;
2929 if (not defined $session_id) { $session_id = 0;}
2930 my ($package, $version, $section, $description);
2931 my $PACKAGES;
2932 my $timestamp = &get_time();
2934 if(not stat("$path.in")) {
2935 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2936 return;
2937 }
2939 open($PACKAGES, "<$path.in");
2940 if(not defined($PACKAGES)) {
2941 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2942 return;
2943 }
2945 # Read lines
2946 while (<$PACKAGES>){
2947 my $line = $_;
2948 # Unify
2949 chop($line);
2951 # Use empty lines as a trigger
2952 if ($line =~ /^\s*$/){
2953 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2954 push(@packages_list_statements, $sql);
2955 $package = "none";
2956 $version = "none";
2957 $section = "none";
2958 $description = "none";
2959 next;
2960 }
2962 # Trigger for package name
2963 if ($line =~ /^Package:\s/){
2964 ($package)= ($line =~ /^Package: (.*)$/);
2965 next;
2966 }
2968 # Trigger for version
2969 if ($line =~ /^Version:\s/){
2970 ($version)= ($line =~ /^Version: (.*)$/);
2971 next;
2972 }
2974 # Trigger for description
2975 if ($line =~ /^Description:\s/){
2976 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2977 next;
2978 }
2980 # Trigger for section
2981 if ($line =~ /^Section:\s/){
2982 ($section)= ($line =~ /^Section: (.*)$/);
2983 next;
2984 }
2986 # Trigger for filename
2987 if ($line =~ /^Filename:\s/){
2988 my ($filename) = ($line =~ /^Filename: (.*)$/);
2989 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2990 next;
2991 }
2992 }
2994 close( $PACKAGES );
2995 unlink( "$path.in" );
2996 }
2999 sub store_fileinfo {
3000 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3002 my %fileinfo = (
3003 'package' => $package,
3004 'dist' => $dist,
3005 'version' => $vers,
3006 );
3008 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3009 }
3012 sub cleanup_and_extract {
3013 my $fileinfo = $repo_files{ $File::Find::name };
3015 if( defined $fileinfo ) {
3016 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3017 my $sql;
3018 my $package = $fileinfo->{ 'package' };
3019 my $newver = $fileinfo->{ 'version' };
3021 mkpath($dir);
3022 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3024 if( -f "$dir/DEBIAN/templates" ) {
3026 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3028 my $tmpl= ""; {
3029 local $/=undef;
3030 open FILE, "$dir/DEBIAN/templates";
3031 $tmpl = &encode_base64(<FILE>);
3032 close FILE;
3033 }
3034 rmtree("$dir/DEBIAN/templates");
3036 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3037 push @packages_list_statements, $sql;
3038 }
3039 }
3041 return;
3042 }
3045 sub register_at_foreign_servers {
3046 my ($kernel) = $_[KERNEL];
3048 # hole alle bekannten server aus known_server_db
3049 my $server_sql = "SELECT * FROM $known_server_tn";
3050 my $server_res = $known_server_db->exec_statement($server_sql);
3052 # no entries in known_server_db
3053 if (not ref(@$server_res[0]) eq "ARRAY") {
3054 # TODO
3055 }
3057 # detect already connected clients
3058 my $client_sql = "SELECT * FROM $known_clients_tn";
3059 my $client_res = $known_clients_db->exec_statement($client_sql);
3061 # send my server details to all other gosa-si-server within the network
3062 foreach my $hit (@$server_res) {
3063 my $hostname = @$hit[0];
3064 my $hostkey = &create_passwd;
3066 # add already connected clients to registration message
3067 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3068 &add_content2xml_hash($myhash, 'key', $hostkey);
3069 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3071 # add locally loaded gosa-si modules to registration message
3072 my $loaded_modules = {};
3073 while (my ($package, $pck_info) = each %$known_modules) {
3074 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3075 foreach my $act_module (keys(%{@$pck_info[2]})) {
3076 $loaded_modules->{$act_module} = "";
3077 }
3078 }
3080 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3082 # add macaddress to registration message
3083 my ($host_ip, $host_port) = split(/:/, $hostname);
3084 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3085 my $network_interface= &get_interface_for_ip($local_ip);
3086 my $host_mac = &get_mac_for_interface($network_interface);
3087 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3089 # build registration message and send it
3090 my $foreign_server_msg = &create_xml_string($myhash);
3091 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3092 }
3094 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3095 return;
3096 }
3099 #==== MAIN = main ==============================================================
3100 # parse commandline options
3101 Getopt::Long::Configure( "bundling" );
3102 GetOptions("h|help" => \&usage,
3103 "c|config=s" => \$cfg_file,
3104 "f|foreground" => \$foreground,
3105 "v|verbose+" => \$verbose,
3106 "no-arp+" => \$no_arp,
3107 );
3109 # read and set config parameters
3110 &check_cmdline_param ;
3111 &read_configfile($cfg_file, %cfg_defaults);
3112 &check_pid;
3114 $SIG{CHLD} = 'IGNORE';
3116 # forward error messages to logfile
3117 if( ! $foreground ) {
3118 open( STDIN, '+>/dev/null' );
3119 open( STDOUT, '+>&STDIN' );
3120 open( STDERR, '+>&STDIN' );
3121 }
3123 # Just fork, if we are not in foreground mode
3124 if( ! $foreground ) {
3125 chdir '/' or die "Can't chdir to /: $!";
3126 $pid = fork;
3127 setsid or die "Can't start a new session: $!";
3128 umask 0;
3129 } else {
3130 $pid = $$;
3131 }
3133 # Do something useful - put our PID into the pid_file
3134 if( 0 != $pid ) {
3135 open( LOCK_FILE, ">$pid_file" );
3136 print LOCK_FILE "$pid\n";
3137 close( LOCK_FILE );
3138 if( !$foreground ) {
3139 exit( 0 )
3140 };
3141 }
3143 # parse head url and revision from svn
3144 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3145 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3146 $server_headURL = defined $1 ? $1 : 'unknown' ;
3147 $server_revision = defined $2 ? $2 : 'unknown' ;
3148 if ($server_headURL =~ /\/tag\// ||
3149 $server_headURL =~ /\/branches\// ) {
3150 $server_status = "stable";
3151 } else {
3152 $server_status = "developmental" ;
3153 }
3155 # Prepare log file
3156 $root_uid = getpwnam('root');
3157 $adm_gid = getgrnam('adm');
3158 chmod(0640, $log_file);
3159 chown($root_uid, $adm_gid, $log_file);
3160 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3162 daemon_log(" ", 1);
3163 daemon_log("$0 started!", 1);
3164 daemon_log("status: $server_status", 1);
3165 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3167 # connect to incoming_db
3168 unlink($incoming_file_name);
3169 $incoming_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3170 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3172 # connect to gosa-si job queue
3173 unlink($job_queue_file_name); ## just for debugging
3174 $job_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3175 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3176 chmod(0660, $job_queue_file_name);
3177 chown($root_uid, $adm_gid, $job_queue_file_name);
3179 # connect to known_clients_db
3180 unlink($known_clients_file_name); ## just for debugging
3181 $known_clients_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3182 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3183 chmod(0660, $known_clients_file_name);
3184 chown($root_uid, $adm_gid, $known_clients_file_name);
3186 # connect to foreign_clients_db
3187 unlink($foreign_clients_file_name);
3188 $foreign_clients_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3189 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3190 chmod(0660, $foreign_clients_file_name);
3191 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3193 # connect to known_server_db
3194 unlink($known_server_file_name);
3195 $known_server_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3196 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3197 chmod(0660, $known_server_file_name);
3198 chown($root_uid, $adm_gid, $known_server_file_name);
3200 # connect to login_usr_db
3201 unlink($login_users_file_name);
3202 $login_users_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3203 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3204 chmod(0660, $login_users_file_name);
3205 chown($root_uid, $adm_gid, $login_users_file_name);
3207 # connect to fai_server_db
3208 unlink($fai_server_file_name);
3209 $fai_server_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3210 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3211 chmod(0660, $fai_server_file_name);
3212 chown($root_uid, $adm_gid, $fai_server_file_name);
3214 # connect to fai_release_db
3215 unlink($fai_release_file_name);
3216 $fai_release_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3217 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3218 chmod(0660, $fai_release_file_name);
3219 chown($root_uid, $adm_gid, $fai_release_file_name);
3221 # connect to packages_list_db
3222 #unlink($packages_list_file_name);
3223 unlink($packages_list_under_construction);
3224 $packages_list_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3225 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3226 chmod(0660, $packages_list_file_name);
3227 chown($root_uid, $adm_gid, $packages_list_file_name);
3229 # connect to messaging_db
3230 unlink($messaging_file_name);
3231 $messaging_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3232 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3233 chmod(0660, $messaging_file_name);
3234 chown($root_uid, $adm_gid, $messaging_file_name);
3237 # create xml object used for en/decrypting
3238 $xml = new XML::Simple();
3241 # foreign servers
3242 my @foreign_server_list;
3244 # add foreign server from cfg file
3245 if ($foreign_server_string ne "") {
3246 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3247 foreach my $foreign_server (@cfg_foreign_server_list) {
3248 push(@foreign_server_list, $foreign_server);
3249 }
3251 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3252 }
3254 # Perform a DNS lookup for server registration if flag is true
3255 if ($dns_lookup eq "true") {
3256 # Add foreign server from dns
3257 my @tmp_servers;
3258 if (not $server_domain) {
3259 # Try our DNS Searchlist
3260 for my $domain(get_dns_domains()) {
3261 chomp($domain);
3262 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3263 if(@$tmp_domains) {
3264 for my $tmp_server(@$tmp_domains) {
3265 push @tmp_servers, $tmp_server;
3266 }
3267 }
3268 }
3269 if(@tmp_servers && length(@tmp_servers)==0) {
3270 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3271 }
3272 } else {
3273 @tmp_servers = &get_server_addresses($server_domain);
3274 if( 0 == @tmp_servers ) {
3275 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3276 }
3277 }
3279 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3281 foreach my $server (@tmp_servers) {
3282 unshift(@foreign_server_list, $server);
3283 }
3284 } else {
3285 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3286 }
3289 # eliminate duplicate entries
3290 @foreign_server_list = &del_doubles(@foreign_server_list);
3291 my $all_foreign_server = join(", ", @foreign_server_list);
3292 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3294 # add all found foreign servers to known_server
3295 my $act_timestamp = &get_time();
3296 foreach my $foreign_server (@foreign_server_list) {
3298 # do not add myself to known_server_db
3299 if (&is_local($foreign_server)) { next; }
3300 ######################################
3302 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3303 primkey=>['hostname'],
3304 hostname=>$foreign_server,
3305 macaddress=>"",
3306 status=>'not_jet_registered',
3307 hostkey=>"none",
3308 loaded_modules => "none",
3309 timestamp=>$act_timestamp,
3310 } );
3311 }
3314 # Import all modules
3315 &import_modules;
3317 # Check wether all modules are gosa-si valid passwd check
3318 &password_check;
3320 # Prepare for using Opsi
3321 if ($opsi_enabled eq "true") {
3322 use JSON::RPC::Client;
3323 use XML::Quote qw(:all);
3324 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3325 $opsi_client = new JSON::RPC::Client;
3326 }
3329 POE::Component::Server::TCP->new(
3330 Alias => "TCP_SERVER",
3331 Port => $server_port,
3332 ClientInput => sub {
3333 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3334 my $session_id = $session->ID;
3335 my $remote_ip = $heap->{'remote_ip'};
3336 push(@msgs_to_decrypt, $input);
3337 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3338 $kernel->yield("msg_to_decrypt");
3339 },
3340 InlineStates => {
3341 msg_to_decrypt => \&msg_to_decrypt,
3342 next_task => \&next_task,
3343 task_result => \&handle_task_result,
3344 task_done => \&handle_task_done,
3345 task_debug => \&handle_task_debug,
3346 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3347 }
3348 );
3350 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3352 # create session for repeatedly checking the job queue for jobs
3353 POE::Session->create(
3354 inline_states => {
3355 _start => \&session_start,
3356 register_at_foreign_servers => \®ister_at_foreign_servers,
3357 sig_handler => \&sig_handler,
3358 next_task => \&next_task,
3359 task_result => \&handle_task_result,
3360 task_done => \&handle_task_done,
3361 task_debug => \&handle_task_debug,
3362 watch_for_next_tasks => \&watch_for_next_tasks,
3363 watch_for_new_messages => \&watch_for_new_messages,
3364 watch_for_delivery_messages => \&watch_for_delivery_messages,
3365 watch_for_done_messages => \&watch_for_done_messages,
3366 watch_for_new_jobs => \&watch_for_new_jobs,
3367 watch_for_modified_jobs => \&watch_for_modified_jobs,
3368 watch_for_done_jobs => \&watch_for_done_jobs,
3369 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3370 watch_for_old_known_clients => \&watch_for_old_known_clients,
3371 create_packages_list_db => \&run_create_packages_list_db,
3372 create_fai_server_db => \&run_create_fai_server_db,
3373 create_fai_release_db => \&run_create_fai_release_db,
3374 recreate_packages_db => \&run_recreate_packages_db,
3375 session_run_result => \&session_run_result,
3376 session_run_debug => \&session_run_debug,
3377 session_run_done => \&session_run_done,
3378 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3379 }
3380 );
3383 POE::Kernel->run();
3384 exit;