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 } else {
460 my $info = eval($mod_name.'::get_module_info()');
461 # Only load module if get_module_info() returns a non-null object
462 if( $info ) {
463 my ($input_address, $input_key, $event_hash) = @{$info};
464 $known_modules->{$mod_name} = $info;
465 daemon_log("0 INFO: module $mod_name loaded", 5);
466 }
467 }
468 }
470 close (DIR);
471 }
473 #=== FUNCTION ================================================================
474 # NAME: password_check
475 # PARAMETERS: nothing
476 # RETURNS: nothing
477 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
478 # the same password
479 #===============================================================================
480 sub password_check {
481 my $passwd_hash = {};
482 while (my ($mod_name, $mod_info) = each %$known_modules) {
483 my $mod_passwd = @$mod_info[1];
484 if (not defined $mod_passwd) { next; }
485 if (not exists $passwd_hash->{$mod_passwd}) {
486 $passwd_hash->{$mod_passwd} = $mod_name;
488 # escalates critical error
489 } else {
490 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
491 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
492 exit( -1 );
493 }
494 }
496 }
499 #=== FUNCTION ================================================================
500 # NAME: sig_int_handler
501 # PARAMETERS: signal - string - signal arose from system
502 # RETURNS: nothing
503 # DESCRIPTION: handels tasks to be done befor signal becomes active
504 #===============================================================================
505 sub sig_int_handler {
506 my ($signal) = @_;
508 # if (defined($ldap_handle)) {
509 # $ldap_handle->disconnect;
510 # }
511 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
514 daemon_log("shutting down gosa-si-server", 1);
515 system("kill `ps -C gosa-si-server -o pid=`");
516 }
517 $SIG{INT} = \&sig_int_handler;
520 sub check_key_and_xml_validity {
521 my ($crypted_msg, $module_key, $session_id) = @_;
522 my $msg;
523 my $msg_hash;
524 my $error_string;
525 eval{
526 $msg = &decrypt_msg($crypted_msg, $module_key);
528 if ($msg =~ /<xml>/i){
529 $msg =~ s/\s+/ /g; # just for better daemon_log
530 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
531 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
533 ##############
534 # check header
535 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
536 my $header_l = $msg_hash->{'header'};
537 if( 1 > @{$header_l} ) { die 'empty header tag'; }
538 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
539 my $header = @{$header_l}[0];
540 if( 0 == length $header) { die 'empty string in header tag'; }
542 ##############
543 # check source
544 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
545 my $source_l = $msg_hash->{'source'};
546 if( 1 > @{$source_l} ) { die 'empty source tag'; }
547 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
548 my $source = @{$source_l}[0];
549 if( 0 == length $source) { die 'source error'; }
551 ##############
552 # check target
553 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
554 my $target_l = $msg_hash->{'target'};
555 if( 1 > @{$target_l} ) { die 'empty target tag'; }
556 }
557 };
558 if($@) {
559 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
560 $msg = undef;
561 $msg_hash = undef;
562 }
564 return ($msg, $msg_hash);
565 }
568 sub check_outgoing_xml_validity {
569 my ($msg, $session_id) = @_;
571 my $msg_hash;
572 eval{
573 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
575 ##############
576 # check header
577 my $header_l = $msg_hash->{'header'};
578 if( 1 != @{$header_l} ) {
579 die 'no or more than one headers specified';
580 }
581 my $header = @{$header_l}[0];
582 if( 0 == length $header) {
583 die 'header has length 0';
584 }
586 ##############
587 # check source
588 my $source_l = $msg_hash->{'source'};
589 if( 1 != @{$source_l} ) {
590 die 'no or more than 1 sources specified';
591 }
592 my $source = @{$source_l}[0];
593 if( 0 == length $source) {
594 die 'source has length 0';
595 }
596 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
597 $source =~ /^GOSA$/i ) {
598 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
599 }
601 ##############
602 # check target
603 my $target_l = $msg_hash->{'target'};
604 if( 0 == @{$target_l} ) {
605 die "no targets specified";
606 }
607 foreach my $target (@$target_l) {
608 if( 0 == length $target) {
609 die "target has length 0";
610 }
611 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
612 $target =~ /^GOSA$/i ||
613 $target =~ /^\*$/ ||
614 $target =~ /KNOWN_SERVER/i ||
615 $target =~ /JOBDB/i ||
616 $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 ){
617 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
618 }
619 }
620 };
621 if($@) {
622 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
623 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
624 $msg_hash = undef;
625 }
627 return ($msg_hash);
628 }
631 sub input_from_known_server {
632 my ($input, $remote_ip, $session_id) = @_ ;
633 my ($msg, $msg_hash, $module);
635 my $sql_statement= "SELECT * FROM known_server";
636 my $query_res = $known_server_db->select_dbentry( $sql_statement );
638 while( my ($hit_num, $hit) = each %{ $query_res } ) {
639 my $host_name = $hit->{hostname};
640 if( not $host_name =~ "^$remote_ip") {
641 next;
642 }
643 my $host_key = $hit->{hostkey};
644 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
645 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
647 # check if module can open msg envelope with module key
648 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
649 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
650 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
651 daemon_log("$@", 8);
652 next;
653 }
654 else {
655 $msg = $tmp_msg;
656 $msg_hash = $tmp_msg_hash;
657 $module = "ServerPackages";
658 last;
659 }
660 }
662 if( (!$msg) || (!$msg_hash) || (!$module) ) {
663 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
664 }
666 return ($msg, $msg_hash, $module);
667 }
670 sub input_from_known_client {
671 my ($input, $remote_ip, $session_id) = @_ ;
672 my ($msg, $msg_hash, $module);
674 my $sql_statement= "SELECT * FROM known_clients";
675 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
676 while( my ($hit_num, $hit) = each %{ $query_res } ) {
677 my $host_name = $hit->{hostname};
678 if( not $host_name =~ /^$remote_ip:\d*$/) {
679 next;
680 }
681 my $host_key = $hit->{hostkey};
682 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
683 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
685 # check if module can open msg envelope with module key
686 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
688 if( (!$msg) || (!$msg_hash) ) {
689 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
690 &daemon_log("$@", 8);
691 next;
692 }
693 else {
694 $module = "ClientPackages";
695 last;
696 }
697 }
699 if( (!$msg) || (!$msg_hash) || (!$module) ) {
700 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
701 }
703 return ($msg, $msg_hash, $module);
704 }
707 sub input_from_unknown_host {
708 no strict "refs";
709 my ($input, $session_id) = @_ ;
710 my ($msg, $msg_hash, $module);
711 my $error_string;
713 my %act_modules = %$known_modules;
715 while( my ($mod, $info) = each(%act_modules)) {
717 # check a key exists for this module
718 my $module_key = ${$mod."_key"};
719 if( not defined $module_key ) {
720 if( $mod eq 'ArpHandler' ) {
721 next;
722 }
723 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
724 next;
725 }
726 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
728 # check if module can open msg envelope with module key
729 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
730 if( (not defined $msg) || (not defined $msg_hash) ) {
731 next;
732 } else {
733 $module = $mod;
734 last;
735 }
736 }
738 if( (!$msg) || (!$msg_hash) || (!$module)) {
739 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
740 }
742 return ($msg, $msg_hash, $module);
743 }
746 sub create_ciphering {
747 my ($passwd) = @_;
748 if((!defined($passwd)) || length($passwd)==0) {
749 $passwd = "";
750 }
751 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
752 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
753 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
754 $my_cipher->set_iv($iv);
755 return $my_cipher;
756 }
759 sub encrypt_msg {
760 my ($msg, $key) = @_;
761 my $my_cipher = &create_ciphering($key);
762 my $len;
763 {
764 use bytes;
765 $len= 16-length($msg)%16;
766 }
767 $msg = "\0"x($len).$msg;
768 $msg = $my_cipher->encrypt($msg);
769 chomp($msg = &encode_base64($msg));
770 # there are no newlines allowed inside msg
771 $msg=~ s/\n//g;
772 return $msg;
773 }
776 sub decrypt_msg {
778 my ($msg, $key) = @_ ;
779 $msg = &decode_base64($msg);
780 my $my_cipher = &create_ciphering($key);
781 $msg = $my_cipher->decrypt($msg);
782 $msg =~ s/\0*//g;
783 return $msg;
784 }
787 sub get_encrypt_key {
788 my ($target) = @_ ;
789 my $encrypt_key;
790 my $error = 0;
792 # target can be in known_server
793 if( not defined $encrypt_key ) {
794 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
795 my $query_res = $known_server_db->select_dbentry( $sql_statement );
796 while( my ($hit_num, $hit) = each %{ $query_res } ) {
797 my $host_name = $hit->{hostname};
798 if( $host_name ne $target ) {
799 next;
800 }
801 $encrypt_key = $hit->{hostkey};
802 last;
803 }
804 }
806 # target can be in known_client
807 if( not defined $encrypt_key ) {
808 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
809 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
810 while( my ($hit_num, $hit) = each %{ $query_res } ) {
811 my $host_name = $hit->{hostname};
812 if( $host_name ne $target ) {
813 next;
814 }
815 $encrypt_key = $hit->{hostkey};
816 last;
817 }
818 }
820 return $encrypt_key;
821 }
824 #=== FUNCTION ================================================================
825 # NAME: open_socket
826 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
827 # [PeerPort] string necessary if port not appended by PeerAddr
828 # RETURNS: socket IO::Socket::INET
829 # DESCRIPTION: open a socket to PeerAddr
830 #===============================================================================
831 sub open_socket {
832 my ($PeerAddr, $PeerPort) = @_ ;
833 if(defined($PeerPort)){
834 $PeerAddr = $PeerAddr.":".$PeerPort;
835 }
836 my $socket;
837 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
838 Porto => "tcp",
839 Type => SOCK_STREAM,
840 Timeout => 5,
841 );
842 if(not defined $socket) {
843 return;
844 }
845 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
846 return $socket;
847 }
850 #sub get_local_ip_for_remote_ip {
851 # my $remote_ip= shift;
852 # my $result="0.0.0.0";
853 #
854 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855 # if($remote_ip eq "127.0.0.1") {
856 # $result = "127.0.0.1";
857 # } else {
858 # my $PROC_NET_ROUTE= ('/proc/net/route');
859 #
860 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861 # or die "Could not open $PROC_NET_ROUTE";
862 #
863 # my @ifs = <PROC_NET_ROUTE>;
864 #
865 # close(PROC_NET_ROUTE);
866 #
867 # # Eat header line
868 # shift @ifs;
869 # chomp @ifs;
870 # foreach my $line(@ifs) {
871 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872 # my $destination;
873 # my $mask;
874 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879 # # destination matches route, save mac and exit
880 # $result= &get_ip($Iface);
881 # last;
882 # }
883 # }
884 # }
885 # } else {
886 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
887 # }
888 # return $result;
889 #}
892 sub send_msg_to_target {
893 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894 my $error = 0;
895 my $header;
896 my $timestamp = &get_time();
897 my $new_status;
898 my $act_status;
899 my ($sql_statement, $res);
901 if( $msg_header ) {
902 $header = "'$msg_header'-";
903 } else {
904 $header = "";
905 }
907 # Patch the source ip
908 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911 }
913 # encrypt xml msg
914 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916 # opensocket
917 my $socket = &open_socket($address);
918 if( !$socket ) {
919 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
920 $error++;
921 }
923 if( $error == 0 ) {
924 # send xml msg
925 print $socket $crypted_msg."\n";
927 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928 daemon_log("$session_id DEBUG: message:\n$msg", 9);
930 }
932 # close socket in any case
933 if( $socket ) {
934 close $socket;
935 }
937 if( $error > 0 ) { $new_status = "down"; }
938 else { $new_status = $msg_header; }
941 # known_clients
942 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
943 $res = $known_clients_db->select_dbentry($sql_statement);
944 if( keys(%$res) == 1) {
945 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
946 if ($act_status eq "down" && $new_status eq "down") {
947 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948 $res = $known_clients_db->del_dbentry($sql_statement);
949 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950 } else {
951 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952 $res = $known_clients_db->update_dbentry($sql_statement);
953 if($new_status eq "down"){
954 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955 } else {
956 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957 }
958 }
959 }
961 # known_server
962 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963 $res = $known_server_db->select_dbentry($sql_statement);
964 if( keys(%$res) == 1) {
965 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
966 if ($act_status eq "down" && $new_status eq "down") {
967 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968 $res = $known_server_db->del_dbentry($sql_statement);
969 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970 }
971 else {
972 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_server_db->update_dbentry($sql_statement);
974 if($new_status eq "down"){
975 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976 } else {
977 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978 }
979 }
980 }
981 return $error;
982 }
985 sub update_jobdb_status_for_send_msgs {
986 my ($answer, $error) = @_;
987 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
988 my $jobdb_id = $1;
990 # sending msg faild
991 if( $error ) {
992 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
993 my $sql_statement = "UPDATE $job_queue_tn ".
994 "SET status='error', result='can not deliver msg, please consult log file' ".
995 "WHERE id=$jobdb_id";
996 my $res = $job_db->update_dbentry($sql_statement);
997 }
999 # sending msg was successful
1000 } else {
1001 my $sql_statement = "UPDATE $job_queue_tn ".
1002 "SET status='done' ".
1003 "WHERE id=$jobdb_id AND status='processed'";
1004 my $res = $job_db->update_dbentry($sql_statement);
1005 }
1006 }
1007 }
1010 sub sig_handler {
1011 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1012 daemon_log("0 INFO got signal '$signal'", 1);
1013 $kernel->sig_handled();
1014 return;
1015 }
1018 sub msg_to_decrypt {
1019 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1020 my $session_id = $session->ID;
1021 my ($msg, $msg_hash, $module);
1022 my $error = 0;
1024 # hole neue msg aus @msgs_to_decrypt
1025 my $next_msg = shift @msgs_to_decrypt;
1027 # entschlüssle sie
1029 # msg is from a new client or gosa
1030 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1031 # msg is from a gosa-si-server
1032 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1034 }
1035 # msg is from a gosa-si-client
1036 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1038 }
1039 # an error occurred
1040 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1042 # could not understand a msg from its server the client cause a re-registering process
1043 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1044 "' to cause a re-registering of the client if necessary", 3);
1045 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1047 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1048 my $host_name = $hit->{'hostname'};
1049 my $host_key = $hit->{'hostkey'};
1050 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053 }
1054 $error++;
1055 }
1058 my $header;
1059 my $target;
1060 my $source;
1061 my $done = 0;
1062 my $sql;
1063 my $res;
1065 # check whether this message should be processed here
1066 if ($error == 0) {
1067 $header = @{$msg_hash->{'header'}}[0];
1068 $target = @{$msg_hash->{'target'}}[0];
1069 $source = @{$msg_hash->{'source'}}[0];
1070 my $not_found_in_known_clients_db = 0;
1071 my $not_found_in_known_server_db = 0;
1072 my $not_found_in_foreign_clients_db = 0;
1073 my $local_address;
1074 my $local_mac;
1075 my ($target_ip, $target_port) = split(':', $target);
1077 # Determine the local ip address if target is an ip address
1078 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1079 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1080 } else {
1081 $local_address = $server_address;
1082 }
1084 # Determine the local mac address if target is a mac address
1085 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) {
1086 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1087 my $network_interface= &get_interface_for_ip($loc_ip);
1088 $local_mac = &get_mac_for_interface($network_interface);
1089 } else {
1090 $local_mac = $server_mac_address;
1091 }
1093 # target and source is equal to GOSA -> process here
1094 if (not $done) {
1095 if ($target eq "GOSA" && $source eq "GOSA") {
1096 $done = 1;
1097 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1098 }
1099 }
1101 # target is own address without forward_to_gosa-tag -> process here
1102 if (not $done) {
1103 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1104 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1105 $done = 1;
1106 if ($source eq "GOSA") {
1107 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1108 }
1109 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1110 }
1111 }
1113 # target is a client address in known_clients -> process here
1114 if (not $done) {
1115 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1116 $res = $known_clients_db->select_dbentry($sql);
1117 if (keys(%$res) > 0) {
1118 $done = 1;
1119 my $hostname = $res->{1}->{'hostname'};
1120 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1121 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1122 if ($source eq "GOSA") {
1123 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1124 }
1125 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1127 } else {
1128 $not_found_in_known_clients_db = 1;
1129 }
1130 }
1132 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1133 if (not $done) {
1134 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1135 my $gosa_at;
1136 my $gosa_session_id;
1137 if (($target eq $local_address) && (defined $forward_to_gosa)){
1138 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1139 if ($gosa_at ne $local_address) {
1140 $done = 1;
1141 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1142 }
1143 }
1144 }
1146 # if message should be processed here -> add message to incoming_db
1147 if ($done) {
1148 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1149 # so gosa-si-server knows how to process this kind of messages
1150 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1151 $module = "GosaPackages";
1152 }
1154 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1155 primkey=>[],
1156 headertag=>$header,
1157 targettag=>$target,
1158 xmlmessage=>&encode_base64($msg),
1159 timestamp=>&get_time,
1160 module=>$module,
1161 sessionid=>$session_id,
1162 } );
1164 }
1166 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1167 if (not $done) {
1168 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1169 my $gosa_at;
1170 my $gosa_session_id;
1171 if (($target eq $local_address) && (defined $forward_to_gosa)){
1172 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1173 if ($gosa_at eq $local_address) {
1174 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1175 if( defined $session_reference ) {
1176 $heap = $session_reference->get_heap();
1177 }
1178 if(exists $heap->{'client'}) {
1179 $msg = &encrypt_msg($msg, $GosaPackages_key);
1180 $heap->{'client'}->put($msg);
1181 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1182 }
1183 $done = 1;
1184 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1185 }
1186 }
1188 }
1190 # target is a client address in foreign_clients -> forward to registration server
1191 if (not $done) {
1192 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1193 $res = $foreign_clients_db->select_dbentry($sql);
1194 if (keys(%$res) > 0) {
1195 my $hostname = $res->{1}->{'hostname'};
1196 my ($host_ip, $host_port) = split(/:/, $hostname);
1197 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1198 my $regserver = $res->{1}->{'regserver'};
1199 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1200 my $res = $known_server_db->select_dbentry($sql);
1201 if (keys(%$res) > 0) {
1202 my $regserver_key = $res->{1}->{'hostkey'};
1203 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1204 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1205 if ($source eq "GOSA") {
1206 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1207 }
1208 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1209 }
1210 $done = 1;
1211 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1212 } else {
1213 $not_found_in_foreign_clients_db = 1;
1214 }
1215 }
1217 # target is a server address -> forward to server
1218 if (not $done) {
1219 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1220 $res = $known_server_db->select_dbentry($sql);
1221 if (keys(%$res) > 0) {
1222 my $hostkey = $res->{1}->{'hostkey'};
1224 if ($source eq "GOSA") {
1225 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1226 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1228 }
1230 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1231 $done = 1;
1232 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1233 } else {
1234 $not_found_in_known_server_db = 1;
1235 }
1236 }
1239 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1240 if ( $not_found_in_foreign_clients_db
1241 && $not_found_in_known_server_db
1242 && $not_found_in_known_clients_db) {
1243 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1244 primkey=>[],
1245 headertag=>$header,
1246 targettag=>$target,
1247 xmlmessage=>&encode_base64($msg),
1248 timestamp=>&get_time,
1249 module=>$module,
1250 sessionid=>$session_id,
1251 } );
1252 $done = 1;
1253 &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);
1254 }
1257 if (not $done) {
1258 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1259 if ($source eq "GOSA") {
1260 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1261 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1263 my $session_reference = $kernel->ID_id_to_session($session_id);
1264 if( defined $session_reference ) {
1265 $heap = $session_reference->get_heap();
1266 }
1267 if(exists $heap->{'client'}) {
1268 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1269 $heap->{'client'}->put($error_msg);
1270 }
1271 }
1272 }
1274 }
1276 return;
1277 }
1280 sub next_task {
1281 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1282 my $running_task = POE::Wheel::Run->new(
1283 Program => sub { process_task($session, $heap, $task) },
1284 StdioFilter => POE::Filter::Reference->new(),
1285 StdoutEvent => "task_result",
1286 StderrEvent => "task_debug",
1287 CloseEvent => "task_done",
1288 );
1289 $heap->{task}->{ $running_task->ID } = $running_task;
1290 }
1292 sub handle_task_result {
1293 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1294 my $client_answer = $result->{'answer'};
1295 if( $client_answer =~ s/session_id=(\d+)$// ) {
1296 my $session_id = $1;
1297 if( defined $session_id ) {
1298 my $session_reference = $kernel->ID_id_to_session($session_id);
1299 if( defined $session_reference ) {
1300 $heap = $session_reference->get_heap();
1301 }
1302 }
1304 if(exists $heap->{'client'}) {
1305 $heap->{'client'}->put($client_answer);
1306 }
1307 }
1308 $kernel->sig(CHLD => "child_reap");
1309 }
1311 sub handle_task_debug {
1312 my $result = $_[ARG0];
1313 print STDERR "$result\n";
1314 }
1316 sub handle_task_done {
1317 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1318 delete $heap->{task}->{$task_id};
1319 }
1321 sub process_task {
1322 no strict "refs";
1323 #CHECK: Not @_[...]?
1324 my ($session, $heap, $task) = @_;
1325 my $error = 0;
1326 my $answer_l;
1327 my ($answer_header, @answer_target_l, $answer_source);
1328 my $client_answer = "";
1330 # prepare all variables needed to process message
1331 #my $msg = $task->{'xmlmessage'};
1332 my $msg = &decode_base64($task->{'xmlmessage'});
1333 my $incoming_id = $task->{'id'};
1334 my $module = $task->{'module'};
1335 my $header = $task->{'headertag'};
1336 my $session_id = $task->{'sessionid'};
1337 my $msg_hash;
1338 eval {
1339 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1340 };
1341 daemon_log("ERROR: XML failure '$@'") if ($@);
1342 my $source = @{$msg_hash->{'source'}}[0];
1344 # set timestamp of incoming client uptodate, so client will not
1345 # be deleted from known_clients because of expiration
1346 my $act_time = &get_time();
1347 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1348 my $res = $known_clients_db->exec_statement($sql);
1350 ######################
1351 # process incoming msg
1352 if( $error == 0) {
1353 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1354 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1355 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1357 if ( 0 < @{$answer_l} ) {
1358 my $answer_str = join("\n", @{$answer_l});
1359 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1360 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1361 }
1362 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1363 } else {
1364 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1365 }
1367 }
1368 if( !$answer_l ) { $error++ };
1370 ########
1371 # answer
1372 if( $error == 0 ) {
1374 foreach my $answer ( @{$answer_l} ) {
1375 # check outgoing msg to xml validity
1376 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1377 if( not defined $answer_hash ) { next; }
1379 $answer_header = @{$answer_hash->{'header'}}[0];
1380 @answer_target_l = @{$answer_hash->{'target'}};
1381 $answer_source = @{$answer_hash->{'source'}}[0];
1383 # deliver msg to all targets
1384 foreach my $answer_target ( @answer_target_l ) {
1386 # targets of msg are all gosa-si-clients in known_clients_db
1387 if( $answer_target eq "*" ) {
1388 # answer is for all clients
1389 my $sql_statement= "SELECT * FROM known_clients";
1390 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1391 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1392 my $host_name = $hit->{hostname};
1393 my $host_key = $hit->{hostkey};
1394 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1395 &update_jobdb_status_for_send_msgs($answer, $error);
1396 }
1397 }
1399 # targets of msg are all gosa-si-server in known_server_db
1400 elsif( $answer_target eq "KNOWN_SERVER" ) {
1401 # answer is for all server in known_server
1402 my $sql_statement= "SELECT * FROM $known_server_tn";
1403 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1404 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1405 my $host_name = $hit->{hostname};
1406 my $host_key = $hit->{hostkey};
1407 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1408 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1409 &update_jobdb_status_for_send_msgs($answer, $error);
1410 }
1411 }
1413 # target of msg is GOsa
1414 elsif( $answer_target eq "GOSA" ) {
1415 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1416 my $add_on = "";
1417 if( defined $session_id ) {
1418 $add_on = ".session_id=$session_id";
1419 }
1420 # answer is for GOSA and has to returned to connected client
1421 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1422 $client_answer = $gosa_answer.$add_on;
1423 }
1425 # target of msg is job queue at this host
1426 elsif( $answer_target eq "JOBDB") {
1427 $answer =~ /<header>(\S+)<\/header>/;
1428 my $header;
1429 if( defined $1 ) { $header = $1; }
1430 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1431 &update_jobdb_status_for_send_msgs($answer, $error);
1432 }
1434 # Target of msg is a mac address
1435 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 ) {
1436 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1437 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1438 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1439 my $found_ip_flag = 0;
1440 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1441 my $host_name = $hit->{hostname};
1442 my $host_key = $hit->{hostkey};
1443 $answer =~ s/$answer_target/$host_name/g;
1444 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1445 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1446 &update_jobdb_status_for_send_msgs($answer, $error);
1447 $found_ip_flag++ ;
1448 }
1449 if ($found_ip_flag == 0) {
1450 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1451 my $res = $foreign_clients_db->select_dbentry($sql);
1452 while( my ($hit_num, $hit) = each %{ $res } ) {
1453 my $host_name = $hit->{hostname};
1454 my $reg_server = $hit->{regserver};
1455 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1457 # Fetch key for reg_server
1458 my $reg_server_key;
1459 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1460 my $res = $known_server_db->select_dbentry($sql);
1461 if (exists $res->{1}) {
1462 $reg_server_key = $res->{1}->{'hostkey'};
1463 } else {
1464 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1465 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1466 $reg_server_key = undef;
1467 }
1469 # Send answer to server where client is registered
1470 if (defined $reg_server_key) {
1471 $answer =~ s/$answer_target/$host_name/g;
1472 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1473 &update_jobdb_status_for_send_msgs($answer, $error);
1474 $found_ip_flag++ ;
1475 }
1476 }
1477 }
1478 if( $found_ip_flag == 0) {
1479 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1480 }
1482 # Answer is for one specific host
1483 } else {
1484 # get encrypt_key
1485 my $encrypt_key = &get_encrypt_key($answer_target);
1486 if( not defined $encrypt_key ) {
1487 # unknown target
1488 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1489 next;
1490 }
1491 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1492 &update_jobdb_status_for_send_msgs($answer, $error);
1493 }
1494 }
1495 }
1496 }
1498 my $filter = POE::Filter::Reference->new();
1499 my %result = (
1500 status => "seems ok to me",
1501 answer => $client_answer,
1502 );
1504 my $output = $filter->put( [ \%result ] );
1505 print @$output;
1508 }
1510 sub session_start {
1511 my ($kernel) = $_[KERNEL];
1512 $global_kernel = $kernel;
1513 $kernel->yield('register_at_foreign_servers');
1514 $kernel->yield('create_fai_server_db', $fai_server_tn );
1515 $kernel->yield('create_fai_release_db', $fai_release_tn );
1516 $kernel->yield('watch_for_next_tasks');
1517 $kernel->sig(USR1 => "sig_handler");
1518 $kernel->sig(USR2 => "recreate_packages_db");
1519 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1520 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1521 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1522 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1523 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1524 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1525 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1527 # Start opsi check
1528 if ($opsi_enabled eq "true") {
1529 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1530 }
1532 }
1535 sub watch_for_done_jobs {
1536 #CHECK: $heap for what?
1537 my ($kernel,$heap) = @_[KERNEL, HEAP];
1539 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1540 my $res = $job_db->select_dbentry( $sql_statement );
1542 while( my ($id, $hit) = each %{$res} ) {
1543 my $jobdb_id = $hit->{id};
1544 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1545 my $res = $job_db->del_dbentry($sql_statement);
1546 }
1548 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1549 }
1552 sub watch_for_opsi_jobs {
1553 my ($kernel) = $_[KERNEL];
1555 # 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
1556 # opsi install job is to parse the xml message. There is still the correct header.
1557 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1558 my $res = $job_db->select_dbentry( $sql_statement );
1560 # Ask OPSI for an update of the running jobs
1561 while (my ($id, $hit) = each %$res ) {
1562 # Determine current parameters of the job
1563 my $hostId = $hit->{'plainname'};
1564 my $macaddress = $hit->{'macaddress'};
1565 my $progress = $hit->{'progress'};
1567 my $result= {};
1569 # For hosts, only return the products that are or get installed
1570 my $callobj;
1571 $callobj = {
1572 method => 'getProductStates_hash',
1573 params => [ $hostId ],
1574 id => 1,
1575 };
1577 my $hres = $opsi_client->call($opsi_url, $callobj);
1578 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1579 if (not &check_opsi_res($hres)) {
1580 my $htmp= $hres->result->{$hostId};
1582 # Check state != not_installed or action == setup -> load and add
1583 my $products= 0;
1584 my $installed= 0;
1585 my $installing = 0;
1586 my $error= 0;
1587 my @installed_list;
1588 my @error_list;
1589 my $act_status = "none";
1590 foreach my $product (@{$htmp}){
1592 if ($product->{'installationStatus'} ne "not_installed" or
1593 $product->{'actionRequest'} eq "setup"){
1595 # Increase number of products for this host
1596 $products++;
1598 if ($product->{'installationStatus'} eq "failed"){
1599 $result->{$product->{'productId'}}= "error";
1600 unshift(@error_list, $product->{'productId'});
1601 $error++;
1602 }
1603 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1604 $result->{$product->{'productId'}}= "installed";
1605 unshift(@installed_list, $product->{'productId'});
1606 $installed++;
1607 }
1608 if ($product->{'installationStatus'} eq "installing"){
1609 $result->{$product->{'productId'}}= "installing";
1610 $installing++;
1611 $act_status = "installing - ".$product->{'productId'};
1612 }
1613 }
1614 }
1616 # Estimate "rough" progress, avoid division by zero
1617 if ($products == 0) {
1618 $result->{'progress'}= 0;
1619 } else {
1620 $result->{'progress'}= int($installed * 100 / $products);
1621 }
1623 # Set updates in job queue
1624 if ((not $error) && (not $installing) && ($installed)) {
1625 $act_status = "installed - ".join(", ", @installed_list);
1626 }
1627 if ($error) {
1628 $act_status = "error - ".join(", ", @error_list);
1629 }
1630 if ($progress ne $result->{'progress'} ) {
1631 # Updating progress and result
1632 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1633 my $update_res = $job_db->update_dbentry($update_statement);
1634 }
1635 if ($progress eq 100) {
1636 # Updateing status
1637 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1638 if ($error) {
1639 $done_statement .= "status='error'";
1640 } else {
1641 $done_statement .= "status='done'";
1642 }
1643 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1644 my $done_res = $job_db->update_dbentry($done_statement);
1645 }
1648 }
1649 }
1651 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1652 }
1655 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1656 sub watch_for_modified_jobs {
1657 my ($kernel,$heap) = @_[KERNEL, HEAP];
1659 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1660 my $res = $job_db->select_dbentry( $sql_statement );
1662 # if db contains no jobs which should be update, do nothing
1663 if (keys %$res != 0) {
1665 if ($job_synchronization eq "true") {
1666 # make out of the db result a gosa-si message
1667 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1669 # update all other SI-server
1670 &inform_all_other_si_server($update_msg);
1671 }
1673 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1674 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1675 $res = $job_db->update_dbentry($sql_statement);
1676 }
1678 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1679 }
1682 sub watch_for_new_jobs {
1683 if($watch_for_new_jobs_in_progress == 0) {
1684 $watch_for_new_jobs_in_progress = 1;
1685 my ($kernel,$heap) = @_[KERNEL, HEAP];
1687 # check gosa job quaeue for jobs with executable timestamp
1688 my $timestamp = &get_time();
1689 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1690 my $res = $job_db->exec_statement( $sql_statement );
1692 # Merge all new jobs that would do the same actions
1693 my @drops;
1694 my $hits;
1695 foreach my $hit (reverse @{$res} ) {
1696 my $macaddress= lc @{$hit}[8];
1697 my $headertag= @{$hit}[5];
1698 if(
1699 defined($hits->{$macaddress}) &&
1700 defined($hits->{$macaddress}->{$headertag}) &&
1701 defined($hits->{$macaddress}->{$headertag}[0])
1702 ) {
1703 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1704 }
1705 $hits->{$macaddress}->{$headertag}= $hit;
1706 }
1708 # Delete new jobs with a matching job in state 'processing'
1709 foreach my $macaddress (keys %{$hits}) {
1710 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1711 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1712 if(defined($jobdb_id)) {
1713 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1714 my $res = $job_db->exec_statement( $sql_statement );
1715 foreach my $hit (@{$res}) {
1716 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1717 }
1718 } else {
1719 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1720 }
1721 }
1722 }
1724 # Commit deletion
1725 $job_db->exec_statementlist(\@drops);
1727 # Look for new jobs that could be executed
1728 foreach my $macaddress (keys %{$hits}) {
1730 # Look if there is an executing job
1731 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1732 my $res = $job_db->exec_statement( $sql_statement );
1734 # Skip new jobs for host if there is a processing job
1735 if(defined($res) and defined @{$res}[0]) {
1736 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1737 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1738 if(@{$row}[5] eq 'trigger_action_reinstall') {
1739 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1740 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1741 if(defined($res_2) and defined @{$res_2}[0]) {
1742 # Set status from goto-activation to 'waiting' and update timestamp
1743 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1744 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1745 }
1746 }
1747 next;
1748 }
1750 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1751 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1752 if(defined($jobdb_id)) {
1753 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1755 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1756 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1757 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1759 # expect macaddress is unique!!!!!!
1760 my $target = $res_hash->{1}->{hostname};
1762 # change header
1763 $job_msg =~ s/<header>job_/<header>gosa_/;
1765 # add sqlite_id
1766 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1768 $job_msg =~ /<header>(\S+)<\/header>/;
1769 my $header = $1 ;
1770 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1772 # update status in job queue to 'processing'
1773 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1774 my $res = $job_db->update_dbentry($sql_statement);
1775 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1777 # We don't want parallel processing
1778 last;
1779 }
1780 }
1781 }
1783 $watch_for_new_jobs_in_progress = 0;
1784 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1785 }
1786 }
1789 sub watch_for_new_messages {
1790 my ($kernel,$heap) = @_[KERNEL, HEAP];
1791 my @coll_user_msg; # collection list of outgoing messages
1793 # check messaging_db for new incoming messages with executable timestamp
1794 my $timestamp = &get_time();
1795 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1796 my $res = $messaging_db->exec_statement( $sql_statement );
1797 foreach my $hit (@{$res}) {
1799 # create outgoing messages
1800 my $message_to = @{$hit}[3];
1801 # translate message_to to plain login name
1802 my @message_to_l = split(/,/, $message_to);
1803 my %receiver_h;
1804 foreach my $receiver (@message_to_l) {
1805 if ($receiver =~ /^u_([\s\S]*)$/) {
1806 $receiver_h{$1} = 0;
1807 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1808 my $group_name = $1;
1809 # fetch all group members from ldap and add them to receiver hash
1810 my $ldap_handle = &get_ldap_handle();
1811 if (defined $ldap_handle) {
1812 my $mesg = $ldap_handle->search(
1813 base => $ldap_base,
1814 scope => 'sub',
1815 attrs => ['memberUid'],
1816 filter => "cn=$group_name",
1817 );
1818 if ($mesg->count) {
1819 my @entries = $mesg->entries;
1820 foreach my $entry (@entries) {
1821 my @receivers= $entry->get_value("memberUid");
1822 foreach my $receiver (@receivers) {
1823 $receiver_h{$1} = 0;
1824 }
1825 }
1826 }
1827 # translating errors ?
1828 if ($mesg->code) {
1829 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1830 }
1831 # ldap handle error ?
1832 } else {
1833 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1834 }
1835 } else {
1836 my $sbjct = &encode_base64(@{$hit}[1]);
1837 my $msg = &encode_base64(@{$hit}[7]);
1838 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1839 }
1840 }
1841 my @receiver_l = keys(%receiver_h);
1843 my $message_id = @{$hit}[0];
1845 #add each outgoing msg to messaging_db
1846 my $receiver;
1847 foreach $receiver (@receiver_l) {
1848 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1849 "VALUES ('".
1850 $message_id."', '". # id
1851 @{$hit}[1]."', '". # subject
1852 @{$hit}[2]."', '". # message_from
1853 $receiver."', '". # message_to
1854 "none"."', '". # flag
1855 "out"."', '". # direction
1856 @{$hit}[6]."', '". # delivery_time
1857 @{$hit}[7]."', '". # message
1858 $timestamp."'". # timestamp
1859 ")";
1860 &daemon_log("M DEBUG: $sql_statement", 1);
1861 my $res = $messaging_db->exec_statement($sql_statement);
1862 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1863 }
1865 # set incoming message to flag d=deliverd
1866 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1867 &daemon_log("M DEBUG: $sql_statement", 7);
1868 $res = $messaging_db->update_dbentry($sql_statement);
1869 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1870 }
1872 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1873 return;
1874 }
1876 sub watch_for_delivery_messages {
1877 my ($kernel, $heap) = @_[KERNEL, HEAP];
1879 # select outgoing messages
1880 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1881 #&daemon_log("0 DEBUG: $sql", 7);
1882 my $res = $messaging_db->exec_statement( $sql_statement );
1884 # build out msg for each usr
1885 foreach my $hit (@{$res}) {
1886 my $receiver = @{$hit}[3];
1887 my $msg_id = @{$hit}[0];
1888 my $subject = @{$hit}[1];
1889 my $message = @{$hit}[7];
1891 # resolve usr -> host where usr is logged in
1892 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1893 #&daemon_log("0 DEBUG: $sql", 7);
1894 my $res = $login_users_db->exec_statement($sql);
1896 # reciver is logged in nowhere
1897 if (not ref(@$res[0]) eq "ARRAY") { next; }
1899 my $send_succeed = 0;
1900 foreach my $hit (@$res) {
1901 my $receiver_host = @$hit[0];
1902 my $delivered2host = 0;
1903 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1905 # Looking for host in know_clients_db
1906 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1907 my $res = $known_clients_db->exec_statement($sql);
1909 # Host is known in known_clients_db
1910 if (ref(@$res[0]) eq "ARRAY") {
1911 my $receiver_key = @{@{$res}[0]}[2];
1912 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1913 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1914 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1915 if ($error == 0 ) {
1916 $send_succeed++ ;
1917 $delivered2host++ ;
1918 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1919 } else {
1920 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1921 }
1922 }
1924 # Message already send, do not need to do anything more, otherwise ...
1925 if ($delivered2host) { next;}
1927 # ...looking for host in foreign_clients_db
1928 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1929 $res = $foreign_clients_db->exec_statement($sql);
1931 # Host is known in foreign_clients_db
1932 if (ref(@$res[0]) eq "ARRAY") {
1933 my $registration_server = @{@{$res}[0]}[2];
1935 # Fetch encryption key for registration server
1936 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1937 my $res = $known_server_db->exec_statement($sql);
1938 if (ref(@$res[0]) eq "ARRAY") {
1939 my $registration_server_key = @{@{$res}[0]}[3];
1940 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1941 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1942 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1943 if ($error == 0 ) {
1944 $send_succeed++ ;
1945 $delivered2host++ ;
1946 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1947 } else {
1948 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1949 }
1951 } else {
1952 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1953 "registrated at server '$registration_server', ".
1954 "but no data available in known_server_db ", 1);
1955 }
1956 }
1958 if (not $delivered2host) {
1959 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1960 }
1961 }
1963 if ($send_succeed) {
1964 # set outgoing msg at db to deliverd
1965 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1966 my $res = $messaging_db->exec_statement($sql);
1967 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1968 } else {
1969 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
1970 }
1971 }
1973 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1974 return;
1975 }
1978 sub watch_for_done_messages {
1979 my ($kernel,$heap) = @_[KERNEL, HEAP];
1981 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1982 #&daemon_log("0 DEBUG: $sql", 7);
1983 my $res = $messaging_db->exec_statement($sql);
1985 foreach my $hit (@{$res}) {
1986 my $msg_id = @{$hit}[0];
1988 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1989 #&daemon_log("0 DEBUG: $sql", 7);
1990 my $res = $messaging_db->exec_statement($sql);
1992 # not all usr msgs have been seen till now
1993 if ( ref(@$res[0]) eq "ARRAY") { next; }
1995 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1996 #&daemon_log("0 DEBUG: $sql", 7);
1997 $res = $messaging_db->exec_statement($sql);
1999 }
2001 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2002 return;
2003 }
2006 sub watch_for_old_known_clients {
2007 my ($kernel,$heap) = @_[KERNEL, HEAP];
2009 my $sql_statement = "SELECT * FROM $known_clients_tn";
2010 my $res = $known_clients_db->select_dbentry( $sql_statement );
2012 my $act_time = int(&get_time());
2014 while ( my ($hit_num, $hit) = each %$res) {
2015 my $expired_timestamp = int($hit->{'timestamp'});
2016 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2017 my $dt = DateTime->new( year => $1,
2018 month => $2,
2019 day => $3,
2020 hour => $4,
2021 minute => $5,
2022 second => $6,
2023 );
2025 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2026 $expired_timestamp = $dt->ymd('').$dt->hms('');
2027 if ($act_time > $expired_timestamp) {
2028 my $hostname = $hit->{'hostname'};
2029 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2030 my $del_res = $known_clients_db->exec_statement($del_sql);
2032 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2033 }
2035 }
2037 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2038 }
2041 sub watch_for_next_tasks {
2042 my ($kernel,$heap) = @_[KERNEL, HEAP];
2044 my $sql = "SELECT * FROM $incoming_tn";
2045 my $res = $incoming_db->select_dbentry($sql);
2047 while ( my ($hit_num, $hit) = each %$res) {
2048 my $headertag = $hit->{'headertag'};
2049 if ($headertag =~ /^answer_(\d+)/) {
2050 # do not start processing, this message is for a still running POE::Wheel
2051 next;
2052 }
2053 my $message_id = $hit->{'id'};
2054 $kernel->yield('next_task', $hit);
2056 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2057 my $res = $incoming_db->exec_statement($sql);
2058 }
2060 $kernel->delay_set('watch_for_next_tasks', 1);
2061 }
2064 sub get_ldap_handle {
2065 my ($session_id) = @_;
2066 my $heap;
2067 my $ldap_handle;
2069 if (not defined $session_id ) { $session_id = 0 };
2070 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2072 if ($session_id == 0) {
2073 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2074 $ldap_handle = Net::LDAP->new( $ldap_uri );
2075 if (defined $ldap_handle) {
2076 $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!");
2077 } else {
2078 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2079 }
2081 } else {
2082 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2083 if( defined $session_reference ) {
2084 $heap = $session_reference->get_heap();
2085 }
2087 if (not defined $heap) {
2088 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2089 return;
2090 }
2092 # TODO: This "if" is nonsense, because it doesn't prove that the
2093 # used handle is still valid - or if we've to reconnect...
2094 #if (not exists $heap->{ldap_handle}) {
2095 $ldap_handle = Net::LDAP->new( $ldap_uri );
2096 $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!");
2097 $heap->{ldap_handle} = $ldap_handle;
2098 #}
2099 }
2100 return $ldap_handle;
2101 }
2104 sub change_fai_state {
2105 my ($st, $targets, $session_id) = @_;
2106 $session_id = 0 if not defined $session_id;
2107 # Set FAI state to localboot
2108 my %mapActions= (
2109 reboot => '',
2110 update => 'softupdate',
2111 localboot => 'localboot',
2112 reinstall => 'install',
2113 rescan => '',
2114 wake => '',
2115 memcheck => 'memcheck',
2116 sysinfo => 'sysinfo',
2117 install => 'install',
2118 );
2120 # Return if this is unknown
2121 if (!exists $mapActions{ $st }){
2122 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2123 return;
2124 }
2126 my $state= $mapActions{ $st };
2128 my $ldap_handle = &get_ldap_handle($session_id);
2129 if( defined($ldap_handle) ) {
2131 # Build search filter for hosts
2132 my $search= "(&(objectClass=GOhard)";
2133 foreach (@{$targets}){
2134 $search.= "(macAddress=$_)";
2135 }
2136 $search.= ")";
2138 # If there's any host inside of the search string, procress them
2139 if (!($search =~ /macAddress/)){
2140 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2141 return;
2142 }
2144 # Perform search for Unit Tag
2145 my $mesg = $ldap_handle->search(
2146 base => $ldap_base,
2147 scope => 'sub',
2148 attrs => ['dn', 'FAIstate', 'objectClass'],
2149 filter => "$search"
2150 );
2152 if ($mesg->count) {
2153 my @entries = $mesg->entries;
2154 if (0 == @entries) {
2155 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2156 }
2158 foreach my $entry (@entries) {
2159 # Only modify entry if it is not set to '$state'
2160 if ($entry->get_value("FAIstate") ne "$state"){
2161 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2162 my $result;
2163 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2164 if (exists $tmp{'FAIobject'}){
2165 if ($state eq ''){
2166 $result= $ldap_handle->modify($entry->dn, changes => [
2167 delete => [ FAIstate => [] ] ]);
2168 } else {
2169 $result= $ldap_handle->modify($entry->dn, changes => [
2170 replace => [ FAIstate => $state ] ]);
2171 }
2172 } elsif ($state ne ''){
2173 $result= $ldap_handle->modify($entry->dn, changes => [
2174 add => [ objectClass => 'FAIobject' ],
2175 add => [ FAIstate => $state ] ]);
2176 }
2178 # Errors?
2179 if ($result->code){
2180 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2181 }
2182 } else {
2183 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2184 }
2185 }
2186 } else {
2187 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2188 }
2190 # if no ldap handle defined
2191 } else {
2192 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2193 }
2195 return;
2196 }
2199 sub change_goto_state {
2200 my ($st, $targets, $session_id) = @_;
2201 $session_id = 0 if not defined $session_id;
2203 # Switch on or off?
2204 my $state= $st eq 'active' ? 'active': 'locked';
2206 my $ldap_handle = &get_ldap_handle($session_id);
2207 if( defined($ldap_handle) ) {
2209 # Build search filter for hosts
2210 my $search= "(&(objectClass=GOhard)";
2211 foreach (@{$targets}){
2212 $search.= "(macAddress=$_)";
2213 }
2214 $search.= ")";
2216 # If there's any host inside of the search string, procress them
2217 if (!($search =~ /macAddress/)){
2218 return;
2219 }
2221 # Perform search for Unit Tag
2222 my $mesg = $ldap_handle->search(
2223 base => $ldap_base,
2224 scope => 'sub',
2225 attrs => ['dn', 'gotoMode'],
2226 filter => "$search"
2227 );
2229 if ($mesg->count) {
2230 my @entries = $mesg->entries;
2231 foreach my $entry (@entries) {
2233 # Only modify entry if it is not set to '$state'
2234 if ($entry->get_value("gotoMode") ne $state){
2236 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2237 my $result;
2238 $result= $ldap_handle->modify($entry->dn, changes => [
2239 replace => [ gotoMode => $state ] ]);
2241 # Errors?
2242 if ($result->code){
2243 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2244 }
2246 }
2247 }
2248 } else {
2249 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2250 }
2252 }
2253 }
2256 sub run_recreate_packages_db {
2257 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2258 my $session_id = $session->ID;
2259 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2260 $kernel->yield('create_fai_release_db', $fai_release_tn);
2261 $kernel->yield('create_fai_server_db', $fai_server_tn);
2262 return;
2263 }
2266 sub run_create_fai_server_db {
2267 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2268 my $session_id = $session->ID;
2269 my $task = POE::Wheel::Run->new(
2270 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2271 StdoutEvent => "session_run_result",
2272 StderrEvent => "session_run_debug",
2273 CloseEvent => "session_run_done",
2274 );
2276 $heap->{task}->{ $task->ID } = $task;
2277 return;
2278 }
2281 sub create_fai_server_db {
2282 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2283 my $result;
2285 if (not defined $session_id) { $session_id = 0; }
2286 my $ldap_handle = &get_ldap_handle();
2287 if(defined($ldap_handle)) {
2288 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2289 my $mesg= $ldap_handle->search(
2290 base => $ldap_base,
2291 scope => 'sub',
2292 attrs => ['FAIrepository', 'gosaUnitTag'],
2293 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2294 );
2295 if($mesg->{'resultCode'} == 0 &&
2296 $mesg->count != 0) {
2297 foreach my $entry (@{$mesg->{entries}}) {
2298 if($entry->exists('FAIrepository')) {
2299 # Add an entry for each Repository configured for server
2300 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2301 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2302 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2303 $result= $fai_server_db->add_dbentry( {
2304 table => $table_name,
2305 primkey => ['server', 'fai_release', 'tag'],
2306 server => $tmp_url,
2307 fai_release => $tmp_release,
2308 sections => $tmp_sections,
2309 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2310 } );
2311 }
2312 }
2313 }
2314 }
2315 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2317 # TODO: Find a way to post the 'create_packages_list_db' event
2318 if(not defined($dont_create_packages_list)) {
2319 &create_packages_list_db(undef, undef, $session_id);
2320 }
2321 }
2323 $ldap_handle->disconnect;
2324 return $result;
2325 }
2328 sub run_create_fai_release_db {
2329 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2330 my $session_id = $session->ID;
2331 my $task = POE::Wheel::Run->new(
2332 Program => sub { &create_fai_release_db($table_name, $session_id) },
2333 StdoutEvent => "session_run_result",
2334 StderrEvent => "session_run_debug",
2335 CloseEvent => "session_run_done",
2336 );
2338 $heap->{task}->{ $task->ID } = $task;
2339 return;
2340 }
2343 sub create_fai_release_db {
2344 my ($table_name, $session_id) = @_;
2345 my $result;
2347 # used for logging
2348 if (not defined $session_id) { $session_id = 0; }
2350 my $ldap_handle = &get_ldap_handle();
2351 if(defined($ldap_handle)) {
2352 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2353 my $mesg= $ldap_handle->search(
2354 base => $ldap_base,
2355 scope => 'sub',
2356 attrs => [],
2357 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2358 );
2359 if($mesg->{'resultCode'} == 0 &&
2360 $mesg->count != 0) {
2361 # Walk through all possible FAI container ou's
2362 my @sql_list;
2363 my $timestamp= &get_time();
2364 foreach my $ou (@{$mesg->{entries}}) {
2365 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2366 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2367 my @tmp_array=get_fai_release_entries($tmp_classes);
2368 if(@tmp_array) {
2369 foreach my $entry (@tmp_array) {
2370 if(defined($entry) && ref($entry) eq 'HASH') {
2371 my $sql=
2372 "INSERT INTO $table_name "
2373 ."(timestamp, fai_release, class, type, state) VALUES ("
2374 .$timestamp.","
2375 ."'".$entry->{'release'}."',"
2376 ."'".$entry->{'class'}."',"
2377 ."'".$entry->{'type'}."',"
2378 ."'".$entry->{'state'}."')";
2379 push @sql_list, $sql;
2380 }
2381 }
2382 }
2383 }
2384 }
2386 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2387 if(@sql_list) {
2388 unshift @sql_list, "DELETE FROM $table_name";
2389 $fai_release_db->exec_statementlist(\@sql_list);
2390 }
2391 daemon_log("$session_id DEBUG: Done with inserting",7);
2392 }
2393 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2394 }
2395 $ldap_handle->disconnect;
2396 return $result;
2397 }
2399 sub get_fai_types {
2400 my $tmp_classes = shift || return undef;
2401 my @result;
2403 foreach my $type(keys %{$tmp_classes}) {
2404 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2405 my $entry = {
2406 type => $type,
2407 state => $tmp_classes->{$type}[0],
2408 };
2409 push @result, $entry;
2410 }
2411 }
2413 return @result;
2414 }
2416 sub get_fai_state {
2417 my $result = "";
2418 my $tmp_classes = shift || return $result;
2420 foreach my $type(keys %{$tmp_classes}) {
2421 if(defined($tmp_classes->{$type}[0])) {
2422 $result = $tmp_classes->{$type}[0];
2424 # State is equal for all types in class
2425 last;
2426 }
2427 }
2429 return $result;
2430 }
2432 sub resolve_fai_classes {
2433 my ($fai_base, $ldap_handle, $session_id) = @_;
2434 if (not defined $session_id) { $session_id = 0; }
2435 my $result;
2436 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2437 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2438 my $fai_classes;
2440 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2441 my $mesg= $ldap_handle->search(
2442 base => $fai_base,
2443 scope => 'sub',
2444 attrs => ['cn','objectClass','FAIstate'],
2445 filter => $fai_filter,
2446 );
2447 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2449 if($mesg->{'resultCode'} == 0 &&
2450 $mesg->count != 0) {
2451 foreach my $entry (@{$mesg->{entries}}) {
2452 if($entry->exists('cn')) {
2453 my $tmp_dn= $entry->dn();
2455 # Skip classname and ou dn parts for class
2456 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2458 # Skip classes without releases
2459 if((!defined($tmp_release)) || length($tmp_release)==0) {
2460 next;
2461 }
2463 my $tmp_cn= $entry->get_value('cn');
2464 my $tmp_state= $entry->get_value('FAIstate');
2466 my $tmp_type;
2467 # Get FAI type
2468 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2469 if(grep $_ eq $oclass, @possible_fai_classes) {
2470 $tmp_type= $oclass;
2471 last;
2472 }
2473 }
2475 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2476 # A Subrelease
2477 my @sub_releases = split(/,/, $tmp_release);
2479 # Walk through subreleases and build hash tree
2480 my $hash;
2481 while(my $tmp_sub_release = pop @sub_releases) {
2482 $hash .= "\{'$tmp_sub_release'\}->";
2483 }
2484 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2485 } else {
2486 # A branch, no subrelease
2487 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2488 }
2489 } elsif (!$entry->exists('cn')) {
2490 my $tmp_dn= $entry->dn();
2491 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2493 # Skip classes without releases
2494 if((!defined($tmp_release)) || length($tmp_release)==0) {
2495 next;
2496 }
2498 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2499 # A Subrelease
2500 my @sub_releases= split(/,/, $tmp_release);
2502 # Walk through subreleases and build hash tree
2503 my $hash;
2504 while(my $tmp_sub_release = pop @sub_releases) {
2505 $hash .= "\{'$tmp_sub_release'\}->";
2506 }
2507 # Remove the last two characters
2508 chop($hash);
2509 chop($hash);
2511 eval('$fai_classes->'.$hash.'= {}');
2512 } else {
2513 # A branch, no subrelease
2514 if(!exists($fai_classes->{$tmp_release})) {
2515 $fai_classes->{$tmp_release} = {};
2516 }
2517 }
2518 }
2519 }
2521 # The hash is complete, now we can honor the copy-on-write based missing entries
2522 foreach my $release (keys %$fai_classes) {
2523 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2524 }
2525 }
2526 return $result;
2527 }
2529 sub apply_fai_inheritance {
2530 my $fai_classes = shift || return {};
2531 my $tmp_classes;
2533 # Get the classes from the branch
2534 foreach my $class (keys %{$fai_classes}) {
2535 # Skip subreleases
2536 if($class =~ /^ou=.*$/) {
2537 next;
2538 } else {
2539 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2540 }
2541 }
2543 # Apply to each subrelease
2544 foreach my $subrelease (keys %{$fai_classes}) {
2545 if($subrelease =~ /ou=/) {
2546 foreach my $tmp_class (keys %{$tmp_classes}) {
2547 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2548 $fai_classes->{$subrelease}->{$tmp_class} =
2549 deep_copy($tmp_classes->{$tmp_class});
2550 } else {
2551 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2552 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2553 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2554 deep_copy($tmp_classes->{$tmp_class}->{$type});
2555 }
2556 }
2557 }
2558 }
2559 }
2560 }
2562 # Find subreleases in deeper levels
2563 foreach my $subrelease (keys %{$fai_classes}) {
2564 if($subrelease =~ /ou=/) {
2565 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2566 if($subsubrelease =~ /ou=/) {
2567 apply_fai_inheritance($fai_classes->{$subrelease});
2568 }
2569 }
2570 }
2571 }
2573 return $fai_classes;
2574 }
2576 sub get_fai_release_entries {
2577 my $tmp_classes = shift || return;
2578 my $parent = shift || "";
2579 my @result = shift || ();
2581 foreach my $entry (keys %{$tmp_classes}) {
2582 if(defined($entry)) {
2583 if($entry =~ /^ou=.*$/) {
2584 my $release_name = $entry;
2585 $release_name =~ s/ou=//g;
2586 if(length($parent)>0) {
2587 $release_name = $parent."/".$release_name;
2588 }
2589 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2590 foreach my $bufentry(@bufentries) {
2591 push @result, $bufentry;
2592 }
2593 } else {
2594 my @types = get_fai_types($tmp_classes->{$entry});
2595 foreach my $type (@types) {
2596 push @result,
2597 {
2598 'class' => $entry,
2599 'type' => $type->{'type'},
2600 'release' => $parent,
2601 'state' => $type->{'state'},
2602 };
2603 }
2604 }
2605 }
2606 }
2608 return @result;
2609 }
2611 sub deep_copy {
2612 my $this = shift;
2613 if (not ref $this) {
2614 $this;
2615 } elsif (ref $this eq "ARRAY") {
2616 [map deep_copy($_), @$this];
2617 } elsif (ref $this eq "HASH") {
2618 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2619 } else { die "what type is $_?" }
2620 }
2623 sub session_run_result {
2624 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2625 $kernel->sig(CHLD => "child_reap");
2626 }
2628 sub session_run_debug {
2629 my $result = $_[ARG0];
2630 print STDERR "$result\n";
2631 }
2633 sub session_run_done {
2634 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2635 delete $heap->{task}->{$task_id};
2636 }
2639 sub create_sources_list {
2640 my $session_id = shift;
2641 my $ldap_handle = &main::get_ldap_handle;
2642 my $result="/tmp/gosa_si_tmp_sources_list";
2644 # Remove old file
2645 if(stat($result)) {
2646 unlink($result);
2647 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2648 }
2650 my $fh;
2651 open($fh, ">$result");
2652 if (not defined $fh) {
2653 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2654 return undef;
2655 }
2656 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2657 my $mesg=$ldap_handle->search(
2658 base => $main::ldap_server_dn,
2659 scope => 'base',
2660 attrs => 'FAIrepository',
2661 filter => 'objectClass=FAIrepositoryServer'
2662 );
2663 if($mesg->count) {
2664 foreach my $entry(@{$mesg->{'entries'}}) {
2665 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2666 my ($server, $tag, $release, $sections)= split /\|/, $value;
2667 my $line = "deb $server $release";
2668 $sections =~ s/,/ /g;
2669 $line.= " $sections";
2670 print $fh $line."\n";
2671 }
2672 }
2673 }
2674 } else {
2675 if (defined $main::ldap_server_dn){
2676 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2677 } else {
2678 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2679 }
2680 }
2681 close($fh);
2683 return $result;
2684 }
2687 sub run_create_packages_list_db {
2688 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2689 my $session_id = $session->ID;
2691 my $task = POE::Wheel::Run->new(
2692 Priority => +20,
2693 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2694 StdoutEvent => "session_run_result",
2695 StderrEvent => "session_run_debug",
2696 CloseEvent => "session_run_done",
2697 );
2698 $heap->{task}->{ $task->ID } = $task;
2699 }
2702 sub create_packages_list_db {
2703 my ($ldap_handle, $sources_file, $session_id) = @_;
2705 # it should not be possible to trigger a recreation of packages_list_db
2706 # while packages_list_db is under construction, so set flag packages_list_under_construction
2707 # which is tested befor recreation can be started
2708 if (-r $packages_list_under_construction) {
2709 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2710 return;
2711 } else {
2712 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2713 # set packages_list_under_construction to true
2714 system("touch $packages_list_under_construction");
2715 @packages_list_statements=();
2716 }
2718 if (not defined $session_id) { $session_id = 0; }
2719 if (not defined $ldap_handle) {
2720 $ldap_handle= &get_ldap_handle();
2722 if (not defined $ldap_handle) {
2723 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2724 unlink($packages_list_under_construction);
2725 return;
2726 }
2727 }
2728 if (not defined $sources_file) {
2729 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2730 $sources_file = &create_sources_list($session_id);
2731 }
2733 if (not defined $sources_file) {
2734 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2735 unlink($packages_list_under_construction);
2736 return;
2737 }
2739 my $line;
2741 open(CONFIG, "<$sources_file") or do {
2742 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2743 unlink($packages_list_under_construction);
2744 return;
2745 };
2747 # Read lines
2748 while ($line = <CONFIG>){
2749 # Unify
2750 chop($line);
2751 $line =~ s/^\s+//;
2752 $line =~ s/^\s+/ /;
2754 # Strip comments
2755 $line =~ s/#.*$//g;
2757 # Skip empty lines
2758 if ($line =~ /^\s*$/){
2759 next;
2760 }
2762 # Interpret deb line
2763 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2764 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2765 my $section;
2766 foreach $section (split(' ', $sections)){
2767 &parse_package_info( $baseurl, $dist, $section, $session_id );
2768 }
2769 }
2770 }
2772 close (CONFIG);
2775 if(keys(%repo_dirs)) {
2776 find(\&cleanup_and_extract, keys( %repo_dirs ));
2777 &main::strip_packages_list_statements();
2778 $packages_list_db->exec_statementlist(\@packages_list_statements);
2779 }
2780 unlink($packages_list_under_construction);
2781 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2782 return;
2783 }
2785 # This function should do some intensive task to minimize the db-traffic
2786 sub strip_packages_list_statements {
2787 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2788 my @new_statement_list=();
2789 my $hash;
2790 my $insert_hash;
2791 my $update_hash;
2792 my $delete_hash;
2793 my $local_timestamp=get_time();
2795 foreach my $existing_entry (@existing_entries) {
2796 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2797 }
2799 foreach my $statement (@packages_list_statements) {
2800 if($statement =~ /^INSERT/i) {
2801 # Assign the values from the insert statement
2802 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2803 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2804 if(exists($hash->{$distribution}->{$package}->{$version})) {
2805 # If section or description has changed, update the DB
2806 if(
2807 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2808 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2809 ) {
2810 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2811 }
2812 } else {
2813 # Insert a non-existing entry to db
2814 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2815 }
2816 } elsif ($statement =~ /^UPDATE/i) {
2817 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2818 /^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;
2819 foreach my $distribution (keys %{$hash}) {
2820 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2821 # update the insertion hash to execute only one query per package (insert instead insert+update)
2822 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2823 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2824 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2825 my $section;
2826 my $description;
2827 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2828 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2829 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2830 }
2831 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2832 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2833 }
2834 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2835 }
2836 }
2837 }
2838 }
2839 }
2841 # TODO: Check for orphaned entries
2843 # unroll the insert_hash
2844 foreach my $distribution (keys %{$insert_hash}) {
2845 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2846 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2847 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2848 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2849 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2850 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2851 ."'$local_timestamp')";
2852 }
2853 }
2854 }
2856 # unroll the update hash
2857 foreach my $distribution (keys %{$update_hash}) {
2858 foreach my $package (keys %{$update_hash->{$distribution}}) {
2859 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2860 my $set = "";
2861 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2862 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2863 }
2864 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2865 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2866 }
2867 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2868 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2869 }
2870 if(defined($set) and length($set) > 0) {
2871 $set .= "timestamp = '$local_timestamp'";
2872 } else {
2873 next;
2874 }
2875 push @new_statement_list,
2876 "UPDATE $main::packages_list_tn SET $set WHERE"
2877 ." distribution = '$distribution'"
2878 ." AND package = '$package'"
2879 ." AND version = '$version'";
2880 }
2881 }
2882 }
2884 @packages_list_statements = @new_statement_list;
2885 }
2888 sub parse_package_info {
2889 my ($baseurl, $dist, $section, $session_id)= @_;
2890 my ($package);
2891 if (not defined $session_id) { $session_id = 0; }
2892 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2893 $repo_dirs{ "${repo_path}/pool" } = 1;
2895 foreach $package ("Packages.gz"){
2896 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2897 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2898 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2899 }
2901 }
2904 sub get_package {
2905 my ($url, $dest, $session_id)= @_;
2906 if (not defined $session_id) { $session_id = 0; }
2908 my $tpath = dirname($dest);
2909 -d "$tpath" || mkpath "$tpath";
2911 # This is ugly, but I've no time to take a look at "how it works in perl"
2912 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2913 system("gunzip -cd '$dest' > '$dest.in'");
2914 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2915 unlink($dest);
2916 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2917 } else {
2918 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2919 }
2920 return 0;
2921 }
2924 sub parse_package {
2925 my ($path, $dist, $srv_path, $session_id)= @_;
2926 if (not defined $session_id) { $session_id = 0;}
2927 my ($package, $version, $section, $description);
2928 my $PACKAGES;
2929 my $timestamp = &get_time();
2931 if(not stat("$path.in")) {
2932 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2933 return;
2934 }
2936 open($PACKAGES, "<$path.in");
2937 if(not defined($PACKAGES)) {
2938 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2939 return;
2940 }
2942 # Read lines
2943 while (<$PACKAGES>){
2944 my $line = $_;
2945 # Unify
2946 chop($line);
2948 # Use empty lines as a trigger
2949 if ($line =~ /^\s*$/){
2950 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2951 push(@packages_list_statements, $sql);
2952 $package = "none";
2953 $version = "none";
2954 $section = "none";
2955 $description = "none";
2956 next;
2957 }
2959 # Trigger for package name
2960 if ($line =~ /^Package:\s/){
2961 ($package)= ($line =~ /^Package: (.*)$/);
2962 next;
2963 }
2965 # Trigger for version
2966 if ($line =~ /^Version:\s/){
2967 ($version)= ($line =~ /^Version: (.*)$/);
2968 next;
2969 }
2971 # Trigger for description
2972 if ($line =~ /^Description:\s/){
2973 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2974 next;
2975 }
2977 # Trigger for section
2978 if ($line =~ /^Section:\s/){
2979 ($section)= ($line =~ /^Section: (.*)$/);
2980 next;
2981 }
2983 # Trigger for filename
2984 if ($line =~ /^Filename:\s/){
2985 my ($filename) = ($line =~ /^Filename: (.*)$/);
2986 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2987 next;
2988 }
2989 }
2991 close( $PACKAGES );
2992 unlink( "$path.in" );
2993 }
2996 sub store_fileinfo {
2997 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2999 my %fileinfo = (
3000 'package' => $package,
3001 'dist' => $dist,
3002 'version' => $vers,
3003 );
3005 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3006 }
3009 sub cleanup_and_extract {
3010 my $fileinfo = $repo_files{ $File::Find::name };
3012 if( defined $fileinfo ) {
3013 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3014 my $sql;
3015 my $package = $fileinfo->{ 'package' };
3016 my $newver = $fileinfo->{ 'version' };
3018 mkpath($dir);
3019 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3021 if( -f "$dir/DEBIAN/templates" ) {
3023 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3025 my $tmpl= ""; {
3026 local $/=undef;
3027 open FILE, "$dir/DEBIAN/templates";
3028 $tmpl = &encode_base64(<FILE>);
3029 close FILE;
3030 }
3031 rmtree("$dir/DEBIAN/templates");
3033 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3034 push @packages_list_statements, $sql;
3035 }
3036 }
3038 return;
3039 }
3042 sub register_at_foreign_servers {
3043 my ($kernel) = $_[KERNEL];
3045 # hole alle bekannten server aus known_server_db
3046 my $server_sql = "SELECT * FROM $known_server_tn";
3047 my $server_res = $known_server_db->exec_statement($server_sql);
3049 # no entries in known_server_db
3050 if (not ref(@$server_res[0]) eq "ARRAY") {
3051 # TODO
3052 }
3054 # detect already connected clients
3055 my $client_sql = "SELECT * FROM $known_clients_tn";
3056 my $client_res = $known_clients_db->exec_statement($client_sql);
3058 # send my server details to all other gosa-si-server within the network
3059 foreach my $hit (@$server_res) {
3060 my $hostname = @$hit[0];
3061 my $hostkey = &create_passwd;
3063 # add already connected clients to registration message
3064 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3065 &add_content2xml_hash($myhash, 'key', $hostkey);
3066 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3068 # add locally loaded gosa-si modules to registration message
3069 my $loaded_modules = {};
3070 while (my ($package, $pck_info) = each %$known_modules) {
3071 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3072 foreach my $act_module (keys(%{@$pck_info[2]})) {
3073 $loaded_modules->{$act_module} = "";
3074 }
3075 }
3077 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3079 # add macaddress to registration message
3080 my ($host_ip, $host_port) = split(/:/, $hostname);
3081 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3082 my $network_interface= &get_interface_for_ip($local_ip);
3083 my $host_mac = &get_mac_for_interface($network_interface);
3084 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3086 # build registration message and send it
3087 my $foreign_server_msg = &create_xml_string($myhash);
3088 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3089 }
3091 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3092 return;
3093 }
3096 #==== MAIN = main ==============================================================
3097 # parse commandline options
3098 Getopt::Long::Configure( "bundling" );
3099 GetOptions("h|help" => \&usage,
3100 "c|config=s" => \$cfg_file,
3101 "f|foreground" => \$foreground,
3102 "v|verbose+" => \$verbose,
3103 "no-arp+" => \$no_arp,
3104 );
3106 # read and set config parameters
3107 &check_cmdline_param ;
3108 &read_configfile($cfg_file, %cfg_defaults);
3109 &check_pid;
3111 $SIG{CHLD} = 'IGNORE';
3113 # forward error messages to logfile
3114 if( ! $foreground ) {
3115 open( STDIN, '+>/dev/null' );
3116 open( STDOUT, '+>&STDIN' );
3117 open( STDERR, '+>&STDIN' );
3118 }
3120 # Just fork, if we are not in foreground mode
3121 if( ! $foreground ) {
3122 chdir '/' or die "Can't chdir to /: $!";
3123 $pid = fork;
3124 setsid or die "Can't start a new session: $!";
3125 umask 0;
3126 } else {
3127 $pid = $$;
3128 }
3130 # Do something useful - put our PID into the pid_file
3131 if( 0 != $pid ) {
3132 open( LOCK_FILE, ">$pid_file" );
3133 print LOCK_FILE "$pid\n";
3134 close( LOCK_FILE );
3135 if( !$foreground ) {
3136 exit( 0 )
3137 };
3138 }
3140 # parse head url and revision from svn
3141 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3142 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3143 $server_headURL = defined $1 ? $1 : 'unknown' ;
3144 $server_revision = defined $2 ? $2 : 'unknown' ;
3145 if ($server_headURL =~ /\/tag\// ||
3146 $server_headURL =~ /\/branches\// ) {
3147 $server_status = "stable";
3148 } else {
3149 $server_status = "developmental" ;
3150 }
3152 # Prepare log file
3153 $root_uid = getpwnam('root');
3154 $adm_gid = getgrnam('adm');
3155 chmod(0640, $log_file);
3156 chown($root_uid, $adm_gid, $log_file);
3157 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3159 daemon_log(" ", 1);
3160 daemon_log("$0 started!", 1);
3161 daemon_log("status: $server_status", 1);
3162 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3164 # connect to incoming_db
3165 unlink($incoming_file_name);
3166 $incoming_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3167 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3169 # connect to gosa-si job queue
3170 unlink($job_queue_file_name); ## just for debugging
3171 $job_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3172 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3173 chmod(0660, $job_queue_file_name);
3174 chown($root_uid, $adm_gid, $job_queue_file_name);
3176 # connect to known_clients_db
3177 unlink($known_clients_file_name); ## just for debugging
3178 $known_clients_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3179 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3180 chmod(0660, $known_clients_file_name);
3181 chown($root_uid, $adm_gid, $known_clients_file_name);
3183 # connect to foreign_clients_db
3184 unlink($foreign_clients_file_name);
3185 $foreign_clients_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3186 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3187 chmod(0660, $foreign_clients_file_name);
3188 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3190 # connect to known_server_db
3191 unlink($known_server_file_name);
3192 $known_server_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3193 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3194 chmod(0660, $known_server_file_name);
3195 chown($root_uid, $adm_gid, $known_server_file_name);
3197 # connect to login_usr_db
3198 unlink($login_users_file_name);
3199 $login_users_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3200 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3201 chmod(0660, $login_users_file_name);
3202 chown($root_uid, $adm_gid, $login_users_file_name);
3204 # connect to fai_server_db
3205 unlink($fai_server_file_name);
3206 $fai_server_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3207 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3208 chmod(0660, $fai_server_file_name);
3209 chown($root_uid, $adm_gid, $fai_server_file_name);
3211 # connect to fai_release_db
3212 unlink($fai_release_file_name);
3213 $fai_release_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3214 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3215 chmod(0660, $fai_release_file_name);
3216 chown($root_uid, $adm_gid, $fai_release_file_name);
3218 # connect to packages_list_db
3219 #unlink($packages_list_file_name);
3220 unlink($packages_list_under_construction);
3221 $packages_list_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3222 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3223 chmod(0660, $packages_list_file_name);
3224 chown($root_uid, $adm_gid, $packages_list_file_name);
3226 # connect to messaging_db
3227 unlink($messaging_file_name);
3228 $messaging_db = GOSA::DBmysql->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3229 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3230 chmod(0660, $messaging_file_name);
3231 chown($root_uid, $adm_gid, $messaging_file_name);
3234 # create xml object used for en/decrypting
3235 $xml = new XML::Simple();
3238 # foreign servers
3239 my @foreign_server_list;
3241 # add foreign server from cfg file
3242 if ($foreign_server_string ne "") {
3243 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3244 foreach my $foreign_server (@cfg_foreign_server_list) {
3245 push(@foreign_server_list, $foreign_server);
3246 }
3248 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3249 }
3251 # Perform a DNS lookup for server registration if flag is true
3252 if ($dns_lookup eq "true") {
3253 # Add foreign server from dns
3254 my @tmp_servers;
3255 if (not $server_domain) {
3256 # Try our DNS Searchlist
3257 for my $domain(get_dns_domains()) {
3258 chomp($domain);
3259 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3260 if(@$tmp_domains) {
3261 for my $tmp_server(@$tmp_domains) {
3262 push @tmp_servers, $tmp_server;
3263 }
3264 }
3265 }
3266 if(@tmp_servers && length(@tmp_servers)==0) {
3267 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3268 }
3269 } else {
3270 @tmp_servers = &get_server_addresses($server_domain);
3271 if( 0 == @tmp_servers ) {
3272 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3273 }
3274 }
3276 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3278 foreach my $server (@tmp_servers) {
3279 unshift(@foreign_server_list, $server);
3280 }
3281 } else {
3282 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3283 }
3286 # eliminate duplicate entries
3287 @foreign_server_list = &del_doubles(@foreign_server_list);
3288 my $all_foreign_server = join(", ", @foreign_server_list);
3289 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3291 # add all found foreign servers to known_server
3292 my $act_timestamp = &get_time();
3293 foreach my $foreign_server (@foreign_server_list) {
3295 # do not add myself to known_server_db
3296 if (&is_local($foreign_server)) { next; }
3297 ######################################
3299 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3300 primkey=>['hostname'],
3301 hostname=>$foreign_server,
3302 macaddress=>"",
3303 status=>'not_jet_registered',
3304 hostkey=>"none",
3305 loaded_modules => "none",
3306 timestamp=>$act_timestamp,
3307 } );
3308 }
3311 # Import all modules
3312 &import_modules;
3314 # Check wether all modules are gosa-si valid passwd check
3315 &password_check;
3317 # Prepare for using Opsi
3318 if ($opsi_enabled eq "true") {
3319 use JSON::RPC::Client;
3320 use XML::Quote qw(:all);
3321 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3322 $opsi_client = new JSON::RPC::Client;
3323 }
3326 POE::Component::Server::TCP->new(
3327 Alias => "TCP_SERVER",
3328 Port => $server_port,
3329 ClientInput => sub {
3330 my ($kernel, $input) = @_[KERNEL, ARG0];
3331 push(@tasks, $input);
3332 push(@msgs_to_decrypt, $input);
3333 $kernel->yield("msg_to_decrypt");
3334 },
3335 InlineStates => {
3336 msg_to_decrypt => \&msg_to_decrypt,
3337 next_task => \&next_task,
3338 task_result => \&handle_task_result,
3339 task_done => \&handle_task_done,
3340 task_debug => \&handle_task_debug,
3341 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3342 }
3343 );
3345 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3347 # create session for repeatedly checking the job queue for jobs
3348 POE::Session->create(
3349 inline_states => {
3350 _start => \&session_start,
3351 register_at_foreign_servers => \®ister_at_foreign_servers,
3352 sig_handler => \&sig_handler,
3353 next_task => \&next_task,
3354 task_result => \&handle_task_result,
3355 task_done => \&handle_task_done,
3356 task_debug => \&handle_task_debug,
3357 watch_for_next_tasks => \&watch_for_next_tasks,
3358 watch_for_new_messages => \&watch_for_new_messages,
3359 watch_for_delivery_messages => \&watch_for_delivery_messages,
3360 watch_for_done_messages => \&watch_for_done_messages,
3361 watch_for_new_jobs => \&watch_for_new_jobs,
3362 watch_for_modified_jobs => \&watch_for_modified_jobs,
3363 watch_for_done_jobs => \&watch_for_done_jobs,
3364 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3365 watch_for_old_known_clients => \&watch_for_old_known_clients,
3366 create_packages_list_db => \&run_create_packages_list_db,
3367 create_fai_server_db => \&run_create_fai_server_db,
3368 create_fai_release_db => \&run_create_fai_release_db,
3369 recreate_packages_db => \&run_recreate_packages_db,
3370 session_run_result => \&session_run_result,
3371 session_run_debug => \&session_run_debug,
3372 session_run_done => \&session_run_done,
3373 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3374 }
3375 );
3378 POE::Kernel->run();
3379 exit;