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::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file,
91 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92 $arp_activ, $gosa_unit_tag,
93 $GosaPackages_key, $gosa_timeout,
94 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96 $arp_enabled, $arp_interface,
97 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135 "timestamp VARCHAR(14) DEFAULT 'none'",
136 "headertag VARCHAR(255) DEFAULT 'none'",
137 "targettag VARCHAR(255) DEFAULT 'none'",
138 "xmlmessage TEXT",
139 "module VARCHAR(255) DEFAULT 'none'",
140 "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148 "timestamp VARCHAR(14) DEFAULT 'none'",
149 "status VARCHAR(255) DEFAULT 'none'",
150 "result TEXT",
151 "progress VARCHAR(255) DEFAULT 'none'",
152 "headertag VARCHAR(255) DEFAULT 'none'",
153 "targettag VARCHAR(255) DEFAULT 'none'",
154 "xmlmessage TEXT",
155 "macaddress VARCHAR(17) DEFAULT 'none'",
156 "plainname VARCHAR(255) DEFAULT 'none'",
157 "siserver VARCHAR(255) DEFAULT 'none'",
158 "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 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)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients";
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server";
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release";
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386";
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging";
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
208 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai";
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
225 # Lifetime of logged in user information. If no update information comes after n seconds,
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235 },
236 "server" => {
237 "ip" => [\$server_ip, "0.0.0.0"],
238 "port" => [\$server_port, "20081"],
239 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
249 "repo-path" => [\$repo_path, '/srv/www/repository'],
250 "ldap-uri" => [\$ldap_uri, ""],
251 "ldap-base" => [\$ldap_base, ""],
252 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
253 "ldap-admin-password" => [\$ldap_admin_password, ""],
254 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
255 "max-clients" => [\$max_clients, 10],
256 "wol-password" => [\$wake_on_lan_passwd, ""],
257 "mysql-username" => [\$mysql_username, "gosa_si"],
258 "mysql-password" => [\$mysql_password, ""],
259 "mysql-database" => [\$mysql_database, "gosa_si"],
260 "mysql-host" => [\$mysql_host, "127.0.0.1"],
261 },
262 "GOsaPackages" => {
263 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266 "key" => [\$GosaPackages_key, "none"],
267 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268 },
269 "ClientPackages" => {
270 "key" => [\$ClientPackages_key, "none"],
271 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272 },
273 "ServerPackages"=> {
274 "address" => [\$foreign_server_string, ""],
275 "dns-lookup" => [\$dns_lookup, "true"],
276 "domain" => [\$server_domain, ""],
277 "key" => [\$ServerPackages_key, "none"],
278 "key-lifetime" => [\$foreign_servers_register_delay, 120],
279 "job-synchronization-enabled" => [\$job_synchronization, "true"],
280 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281 },
282 "ArpHandler" => {
283 "enabled" => [\$arp_enabled, "true"],
284 "interface" => [\$arp_interface, "all"],
285 },
286 "Opsi" => {
287 "enabled" => [\$opsi_enabled, "false"],
288 "server" => [\$opsi_server, "localhost"],
289 "admin" => [\$opsi_admin, "opsi-admin"],
290 "password" => [\$opsi_password, "secret"],
291 },
293 );
296 #=== FUNCTION ================================================================
297 # NAME: usage
298 # PARAMETERS: nothing
299 # RETURNS: nothing
300 # DESCRIPTION: print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303 print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306 -h : this (help) message
307 -c <file> : config file
308 -f : foreground, process will not be forked to background
309 -v : be verbose (multiple to increase verbosity)
310 -no-arp : starts $prg without connection to arp module
312 EOF
313 print "\n" ;
314 }
317 #=== FUNCTION ================================================================
318 # NAME: logging
319 # PARAMETERS: level - string - default 'info'
320 # msg - string -
321 # facility - string - default 'LOG_DAEMON'
322 # RETURNS: nothing
323 # DESCRIPTION: function for logging
324 #===============================================================================
325 sub daemon_log {
326 # log into log_file
327 my( $msg, $level ) = @_;
328 if(not defined $msg) { return }
329 if(not defined $level) { $level = 1 }
330 if(defined $log_file){
331 open(LOG_HANDLE, ">>$log_file");
332 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333 print STDERR "cannot open $log_file: $!";
334 return
335 }
336 chomp($msg);
337 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
338 if($level <= $verbose){
339 my ($seconds, $minutes, $hours, $monthday, $month,
340 $year, $weekday, $yearday, $sommertime) = localtime(time);
341 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345 $month = $monthnames[$month];
346 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347 $year+=1900;
348 my $name = $prg;
350 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351 print LOG_HANDLE $log_msg;
352 if( $foreground ) {
353 print STDERR $log_msg;
354 }
355 }
356 close( LOG_HANDLE );
357 }
358 }
361 #=== FUNCTION ================================================================
362 # NAME: check_cmdline_param
363 # PARAMETERS: nothing
364 # RETURNS: nothing
365 # DESCRIPTION: validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368 my $err_config;
369 my $err_counter = 0;
370 if(not defined($cfg_file)) {
371 $cfg_file = "/etc/gosa-si/server.conf";
372 if(! -r $cfg_file) {
373 $err_config = "please specify a config file";
374 $err_counter += 1;
375 }
376 }
377 if( $err_counter > 0 ) {
378 &usage( "", 1 );
379 if( defined( $err_config)) { print STDERR "$err_config\n"}
380 print STDERR "\n";
381 exit( -1 );
382 }
383 }
386 #=== FUNCTION ================================================================
387 # NAME: check_pid
388 # PARAMETERS: nothing
389 # RETURNS: nothing
390 # DESCRIPTION: handels pid processing
391 #===============================================================================
392 sub check_pid {
393 $pid = -1;
394 # Check, if we are already running
395 if( open(LOCK_FILE, "<$pid_file") ) {
396 $pid = <LOCK_FILE>;
397 if( defined $pid ) {
398 chomp( $pid );
399 if( -f "/proc/$pid/stat" ) {
400 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401 if( $stat ) {
402 daemon_log("ERROR: Already running",1);
403 close( LOCK_FILE );
404 exit -1;
405 }
406 }
407 }
408 close( LOCK_FILE );
409 unlink( $pid_file );
410 }
412 # create a syslog msg if it is not to possible to open PID file
413 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415 if (open(LOCK_FILE, '<', $pid_file)
416 && ($pid = <LOCK_FILE>))
417 {
418 chomp($pid);
419 $msg .= "(PID $pid)\n";
420 } else {
421 $msg .= "(unable to read PID)\n";
422 }
423 if( ! ($foreground) ) {
424 openlog( $0, "cons,pid", "daemon" );
425 syslog( "warning", $msg );
426 closelog();
427 }
428 else {
429 print( STDERR " $msg " );
430 }
431 exit( -1 );
432 }
433 }
435 #=== FUNCTION ================================================================
436 # NAME: import_modules
437 # PARAMETERS: module_path - string - abs. path to the directory the modules
438 # are stored
439 # RETURNS: nothing
440 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
441 # state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444 daemon_log(" ", 1);
446 if (not -e $modules_path) {
447 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
448 }
450 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451 while (defined (my $file = readdir (DIR))) {
452 if (not $file =~ /(\S*?).pm$/) {
453 next;
454 }
455 my $mod_name = $1;
457 # ArpHandler switch
458 if( $file =~ /ArpHandler.pm/ ) {
459 if( $arp_enabled eq "false" ) { next; }
460 }
462 eval { require $file; };
463 if ($@) {
464 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465 daemon_log("$@", 1);
466 exit;
467 } else {
468 my $info = eval($mod_name.'::get_module_info()');
469 # Only load module if get_module_info() returns a non-null object
470 if( $info ) {
471 my ($input_address, $input_key, $event_hash) = @{$info};
472 $known_modules->{$mod_name} = $info;
473 daemon_log("0 INFO: module $mod_name loaded", 5);
474 }
475 }
476 }
478 close (DIR);
479 }
481 #=== FUNCTION ================================================================
482 # NAME: password_check
483 # PARAMETERS: nothing
484 # RETURNS: nothing
485 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
486 # the same password
487 #===============================================================================
488 sub password_check {
489 my $passwd_hash = {};
490 while (my ($mod_name, $mod_info) = each %$known_modules) {
491 my $mod_passwd = @$mod_info[1];
492 if (not defined $mod_passwd) { next; }
493 if (not exists $passwd_hash->{$mod_passwd}) {
494 $passwd_hash->{$mod_passwd} = $mod_name;
496 # escalates critical error
497 } else {
498 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500 exit( -1 );
501 }
502 }
504 }
507 #=== FUNCTION ================================================================
508 # NAME: sig_int_handler
509 # PARAMETERS: signal - string - signal arose from system
510 # RETURNS: nothing
511 # DESCRIPTION: handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514 my ($signal) = @_;
516 # if (defined($ldap_handle)) {
517 # $ldap_handle->disconnect;
518 # }
519 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
522 daemon_log("shutting down gosa-si-server", 1);
523 system("kill `ps -C gosa-si-server -o pid=`");
524 }
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529 my ($crypted_msg, $module_key, $session_id) = @_;
530 my $msg;
531 my $msg_hash;
532 my $error_string;
533 eval{
534 $msg = &decrypt_msg($crypted_msg, $module_key);
536 if ($msg =~ /<xml>/i){
537 $msg =~ s/\s+/ /g; # just for better daemon_log
538 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541 ##############
542 # check header
543 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544 my $header_l = $msg_hash->{'header'};
545 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
546 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547 my $header = @{$header_l}[0];
548 if( 0 == length $header) { die 'empty string in header tag'; }
550 ##############
551 # check source
552 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553 my $source_l = $msg_hash->{'source'};
554 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
555 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556 my $source = @{$source_l}[0];
557 if( 0 == length $source) { die 'source error'; }
559 ##############
560 # check target
561 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562 my $target_l = $msg_hash->{'target'};
563 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
564 }
565 };
566 if($@) {
567 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568 $msg = undef;
569 $msg_hash = undef;
570 }
572 return ($msg, $msg_hash);
573 }
576 sub check_outgoing_xml_validity {
577 my ($msg, $session_id) = @_;
579 my $msg_hash;
580 eval{
581 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583 ##############
584 # check header
585 my $header_l = $msg_hash->{'header'};
586 if( 1 != @{$header_l} ) {
587 die 'no or more than one headers specified';
588 }
589 my $header = @{$header_l}[0];
590 if( 0 == length $header) {
591 die 'header has length 0';
592 }
594 ##############
595 # check source
596 my $source_l = $msg_hash->{'source'};
597 if( 1 != @{$source_l} ) {
598 die 'no or more than 1 sources specified';
599 }
600 my $source = @{$source_l}[0];
601 if( 0 == length $source) {
602 die 'source has length 0';
603 }
605 # Check if source contains hostname instead of ip address
606 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
607 my ($hostname,$port) = split(/:/, $source);
608 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
609 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
610 # Write ip address to $source variable
611 $source = "$ip_address:$port";
612 }
613 }
614 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
615 $source =~ /^GOSA$/i) {
616 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
617 }
619 ##############
620 # check target
621 my $target_l = $msg_hash->{'target'};
622 if( 0 == @{$target_l} ) {
623 die "no targets specified";
624 }
625 foreach my $target (@$target_l) {
626 if( 0 == length $target) {
627 die "target has length 0";
628 }
629 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
630 $target =~ /^GOSA$/i ||
631 $target =~ /^\*$/ ||
632 $target =~ /KNOWN_SERVER/i ||
633 $target =~ /JOBDB/i ||
634 $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 ){
635 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
636 }
637 }
638 };
639 if($@) {
640 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
641 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
642 $msg_hash = undef;
643 }
645 return ($msg_hash);
646 }
649 sub input_from_known_server {
650 my ($input, $remote_ip, $session_id) = @_ ;
651 my ($msg, $msg_hash, $module);
653 my $sql_statement= "SELECT * FROM known_server";
654 my $query_res = $known_server_db->select_dbentry( $sql_statement );
656 while( my ($hit_num, $hit) = each %{ $query_res } ) {
657 my $host_name = $hit->{hostname};
658 if( not $host_name =~ "^$remote_ip") {
659 next;
660 }
661 my $host_key = $hit->{hostkey};
662 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
663 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
665 # check if module can open msg envelope with module key
666 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
667 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
668 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
669 daemon_log("$@", 8);
670 next;
671 }
672 else {
673 $msg = $tmp_msg;
674 $msg_hash = $tmp_msg_hash;
675 $module = "ServerPackages";
676 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
677 last;
678 }
679 }
681 if( (!$msg) || (!$msg_hash) || (!$module) ) {
682 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
683 }
685 return ($msg, $msg_hash, $module);
686 }
689 sub input_from_known_client {
690 my ($input, $remote_ip, $session_id) = @_ ;
691 my ($msg, $msg_hash, $module);
693 my $sql_statement= "SELECT * FROM known_clients";
694 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
695 while( my ($hit_num, $hit) = each %{ $query_res } ) {
696 my $host_name = $hit->{hostname};
697 if( not $host_name =~ /^$remote_ip:\d*$/) {
698 next;
699 }
700 my $host_key = $hit->{hostkey};
701 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
702 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
704 # check if module can open msg envelope with module key
705 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
707 if( (!$msg) || (!$msg_hash) ) {
708 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
709 &daemon_log("$@", 8);
710 next;
711 }
712 else {
713 $module = "ClientPackages";
714 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
715 last;
716 }
717 }
719 if( (!$msg) || (!$msg_hash) || (!$module) ) {
720 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
721 }
723 return ($msg, $msg_hash, $module);
724 }
727 sub input_from_unknown_host {
728 no strict "refs";
729 my ($input, $session_id) = @_ ;
730 my ($msg, $msg_hash, $module);
731 my $error_string;
733 my %act_modules = %$known_modules;
735 while( my ($mod, $info) = each(%act_modules)) {
737 # check a key exists for this module
738 my $module_key = ${$mod."_key"};
739 if( not defined $module_key ) {
740 if( $mod eq 'ArpHandler' ) {
741 next;
742 }
743 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
744 next;
745 }
746 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
748 # check if module can open msg envelope with module key
749 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
750 if( (not defined $msg) || (not defined $msg_hash) ) {
751 next;
752 } else {
753 $module = $mod;
754 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
755 last;
756 }
757 }
759 if( (!$msg) || (!$msg_hash) || (!$module)) {
760 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
761 }
763 return ($msg, $msg_hash, $module);
764 }
767 sub create_ciphering {
768 my ($passwd) = @_;
769 if((!defined($passwd)) || length($passwd)==0) {
770 $passwd = "";
771 }
772 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
773 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
774 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
775 $my_cipher->set_iv($iv);
776 return $my_cipher;
777 }
780 sub encrypt_msg {
781 my ($msg, $key) = @_;
782 my $my_cipher = &create_ciphering($key);
783 my $len;
784 {
785 use bytes;
786 $len= 16-length($msg)%16;
787 }
788 $msg = "\0"x($len).$msg;
789 $msg = $my_cipher->encrypt($msg);
790 chomp($msg = &encode_base64($msg));
791 # there are no newlines allowed inside msg
792 $msg=~ s/\n//g;
793 return $msg;
794 }
797 sub decrypt_msg {
799 my ($msg, $key) = @_ ;
800 $msg = &decode_base64($msg);
801 my $my_cipher = &create_ciphering($key);
802 $msg = $my_cipher->decrypt($msg);
803 $msg =~ s/\0*//g;
804 return $msg;
805 }
808 sub get_encrypt_key {
809 my ($target) = @_ ;
810 my $encrypt_key;
811 my $error = 0;
813 # target can be in known_server
814 if( not defined $encrypt_key ) {
815 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
816 my $query_res = $known_server_db->select_dbentry( $sql_statement );
817 while( my ($hit_num, $hit) = each %{ $query_res } ) {
818 my $host_name = $hit->{hostname};
819 if( $host_name ne $target ) {
820 next;
821 }
822 $encrypt_key = $hit->{hostkey};
823 last;
824 }
825 }
827 # target can be in known_client
828 if( not defined $encrypt_key ) {
829 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
830 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
831 while( my ($hit_num, $hit) = each %{ $query_res } ) {
832 my $host_name = $hit->{hostname};
833 if( $host_name ne $target ) {
834 next;
835 }
836 $encrypt_key = $hit->{hostkey};
837 last;
838 }
839 }
841 return $encrypt_key;
842 }
845 #=== FUNCTION ================================================================
846 # NAME: open_socket
847 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
848 # [PeerPort] string necessary if port not appended by PeerAddr
849 # RETURNS: socket IO::Socket::INET
850 # DESCRIPTION: open a socket to PeerAddr
851 #===============================================================================
852 sub open_socket {
853 my ($PeerAddr, $PeerPort) = @_ ;
854 if(defined($PeerPort)){
855 $PeerAddr = $PeerAddr.":".$PeerPort;
856 }
857 my $socket;
858 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
859 Porto => "tcp",
860 Type => SOCK_STREAM,
861 Timeout => 5,
862 );
863 if(not defined $socket) {
864 return;
865 }
866 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
867 return $socket;
868 }
871 #sub get_local_ip_for_remote_ip {
872 # my $remote_ip= shift;
873 # my $result="0.0.0.0";
874 #
875 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
876 # if($remote_ip eq "127.0.0.1") {
877 # $result = "127.0.0.1";
878 # } else {
879 # my $PROC_NET_ROUTE= ('/proc/net/route');
880 #
881 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
882 # or die "Could not open $PROC_NET_ROUTE";
883 #
884 # my @ifs = <PROC_NET_ROUTE>;
885 #
886 # close(PROC_NET_ROUTE);
887 #
888 # # Eat header line
889 # shift @ifs;
890 # chomp @ifs;
891 # foreach my $line(@ifs) {
892 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
893 # my $destination;
894 # my $mask;
895 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
896 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
897 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
898 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
899 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
900 # # destination matches route, save mac and exit
901 # $result= &get_ip($Iface);
902 # last;
903 # }
904 # }
905 # }
906 # } else {
907 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
908 # }
909 # return $result;
910 #}
913 sub send_msg_to_target {
914 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
915 my $error = 0;
916 my $header;
917 my $timestamp = &get_time();
918 my $new_status;
919 my $act_status;
920 my ($sql_statement, $res);
922 if( $msg_header ) {
923 $header = "'$msg_header'-";
924 } else {
925 $header = "";
926 }
928 # Patch the source ip
929 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
930 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
931 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
932 }
934 # encrypt xml msg
935 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
937 # opensocket
938 my $socket = &open_socket($address);
939 if( !$socket ) {
940 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
941 $error++;
942 }
944 if( $error == 0 ) {
945 # send xml msg
946 print $socket $crypted_msg."\n";
948 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
949 daemon_log("$session_id DEBUG: message:\n$msg", 9);
951 }
953 # close socket in any case
954 if( $socket ) {
955 close $socket;
956 }
958 if( $error > 0 ) { $new_status = "down"; }
959 else { $new_status = $msg_header; }
962 # known_clients
963 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
964 $res = $known_clients_db->select_dbentry($sql_statement);
965 if( keys(%$res) == 1) {
966 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967 if ($act_status eq "down" && $new_status eq "down") {
968 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
969 $res = $known_clients_db->del_dbentry($sql_statement);
970 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
971 } else {
972 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_clients_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 }
982 # known_server
983 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
984 $res = $known_server_db->select_dbentry($sql_statement);
985 if( keys(%$res) == 1) {
986 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
987 if ($act_status eq "down" && $new_status eq "down") {
988 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
989 $res = $known_server_db->del_dbentry($sql_statement);
990 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
991 }
992 else {
993 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
994 $res = $known_server_db->update_dbentry($sql_statement);
995 if($new_status eq "down"){
996 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
997 } else {
998 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
999 }
1000 }
1001 }
1002 return $error;
1003 }
1006 sub update_jobdb_status_for_send_msgs {
1007 my ($session_id, $answer, $error) = @_;
1008 &daemon_log("$session_id DEBUG: try to update job status", 7);
1009 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1010 my $jobdb_id = $1;
1012 $answer =~ /<header>(.*)<\/header>/;
1013 my $job_header = $1;
1015 $answer =~ /<target>(.*)<\/target>/;
1016 my $job_target = $1;
1018 # Sending msg failed
1019 if( $error ) {
1021 # Set jobs to done, jobs do not need to deliver their message in any case
1022 if (($job_header eq "trigger_action_localboot")
1023 ||($job_header eq "trigger_action_lock")
1024 ||($job_header eq "trigger_action_halt")
1025 ) {
1026 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1027 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1028 my $res = $job_db->update_dbentry($sql_statement);
1030 # Reactivate jobs, jobs need to deliver their message
1031 } elsif (($job_header eq "trigger_action_activate")
1032 ||($job_header eq "trigger_action_update")
1033 ||($job_header eq "trigger_action_reinstall")
1034 ||($job_header eq "trigger_activate_new")
1035 ) {
1036 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1038 # For all other messages
1039 } else {
1040 my $sql_statement = "UPDATE $job_queue_tn ".
1041 "SET status='error', result='can not deliver msg, please consult log file' ".
1042 "WHERE id=$jobdb_id";
1043 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1044 my $res = $job_db->update_dbentry($sql_statement);
1045 }
1047 # Sending msg was successful
1048 } else {
1049 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1050 # jobs reinstall, update, inst_update do themself setting to done
1051 if (($job_header eq "trigger_action_localboot")
1052 ||($job_header eq "trigger_action_lock")
1053 ||($job_header eq "trigger_action_activate")
1054 ||($job_header eq "trigger_action_halt")
1055 ||($job_header eq "trigger_action_reboot")
1056 ||($job_header eq "trigger_action_wake")
1057 ||($job_header eq "trigger_wake")
1058 ) {
1060 my $sql_statement = "UPDATE $job_queue_tn ".
1061 "SET status='done' ".
1062 "WHERE id=$jobdb_id AND status='processed'";
1063 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1064 my $res = $job_db->update_dbentry($sql_statement);
1065 } else {
1066 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7);
1067 }
1068 }
1069 } else {
1070 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7);
1071 }
1072 }
1074 sub reactivate_job_with_delay {
1075 my ($session_id, $target, $header, $delay) = @_ ;
1076 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1078 if (not defined $delay) { $delay = 30 } ;
1079 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1081 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1082 my $res = $job_db->update_dbentry($sql);
1083 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1084 "cause client '$target' is currently not available", 5);
1085 daemon_log("$session_id $sql", 7);
1086 return;
1087 }
1090 sub sig_handler {
1091 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1092 daemon_log("0 INFO got signal '$signal'", 1);
1093 $kernel->sig_handled();
1094 return;
1095 }
1098 sub msg_to_decrypt {
1099 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1100 my $session_id = $session->ID;
1101 my ($msg, $msg_hash, $module);
1102 my $error = 0;
1104 # fetch new msg out of @msgs_to_decrypt
1105 my $tmp_next_msg = shift @msgs_to_decrypt;
1106 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1108 # msg is from a new client or gosa
1109 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1111 # msg is from a gosa-si-server
1112 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1113 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1114 }
1115 # msg is from a gosa-si-client
1116 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1117 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1118 }
1119 # an error occurred
1120 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1121 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1122 # could not understand a msg from its server the client cause a re-registering process
1123 my $remote_ip = $heap->{'remote_ip'};
1124 my $remote_port = $heap->{'remote_port'};
1125 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1126 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1128 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1129 "' to cause a re-registering of the client if necessary", 3);
1130 $error++;
1131 }
1134 my $header;
1135 my $target;
1136 my $source;
1137 my $done = 0;
1138 my $sql;
1139 my $res;
1141 # check whether this message should be processed here
1142 if ($error == 0) {
1143 $header = @{$msg_hash->{'header'}}[0];
1144 $target = @{$msg_hash->{'target'}}[0];
1145 $source = @{$msg_hash->{'source'}}[0];
1146 my $not_found_in_known_clients_db = 0;
1147 my $not_found_in_known_server_db = 0;
1148 my $not_found_in_foreign_clients_db = 0;
1149 my $local_address;
1150 my $local_mac;
1151 my ($target_ip, $target_port) = split(':', $target);
1153 # Determine the local ip address if target is an ip address
1154 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1155 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1156 } else {
1157 $local_address = $server_address;
1158 }
1160 # Determine the local mac address if target is a mac address
1161 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) {
1162 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1163 my $network_interface= &get_interface_for_ip($loc_ip);
1164 $local_mac = &get_mac_for_interface($network_interface);
1165 } else {
1166 $local_mac = $server_mac_address;
1167 }
1169 # target and source is equal to GOSA -> process here
1170 if (not $done) {
1171 if ($target eq "GOSA" && $source eq "GOSA") {
1172 $done = 1;
1173 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1174 }
1175 }
1177 # target is own address without forward_to_gosa-tag -> process here
1178 if (not $done) {
1179 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1180 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1181 $done = 1;
1182 if ($source eq "GOSA") {
1183 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1184 }
1185 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1186 }
1187 }
1189 # target is a client address in known_clients -> process here
1190 if (not $done) {
1191 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1192 $res = $known_clients_db->select_dbentry($sql);
1193 if (keys(%$res) > 0) {
1194 $done = 1;
1195 my $hostname = $res->{1}->{'hostname'};
1196 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1197 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1198 if ($source eq "GOSA") {
1199 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1200 }
1201 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1203 } else {
1204 $not_found_in_known_clients_db = 1;
1205 }
1206 }
1208 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1209 if (not $done) {
1210 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1211 my $gosa_at;
1212 my $gosa_session_id;
1213 if (($target eq $local_address) && (defined $forward_to_gosa)){
1214 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1215 if ($gosa_at ne $local_address) {
1216 $done = 1;
1217 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1218 }
1219 }
1220 }
1222 # if message should be processed here -> add message to incoming_db
1223 if ($done) {
1224 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1225 # so gosa-si-server knows how to process this kind of messages
1226 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1227 $module = "GosaPackages";
1228 }
1230 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1231 primkey=>[],
1232 headertag=>$header,
1233 targettag=>$target,
1234 xmlmessage=>&encode_base64($msg),
1235 timestamp=>&get_time,
1236 module=>$module,
1237 sessionid=>$session_id,
1238 } );
1240 }
1242 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1243 if (not $done) {
1244 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1245 my $gosa_at;
1246 my $gosa_session_id;
1247 if (($target eq $local_address) && (defined $forward_to_gosa)){
1248 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1249 if ($gosa_at eq $local_address) {
1250 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1251 if( defined $session_reference ) {
1252 $heap = $session_reference->get_heap();
1253 }
1254 if(exists $heap->{'client'}) {
1255 $msg = &encrypt_msg($msg, $GosaPackages_key);
1256 $heap->{'client'}->put($msg);
1257 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1258 }
1259 $done = 1;
1260 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1261 }
1262 }
1264 }
1266 # target is a client address in foreign_clients -> forward to registration server
1267 if (not $done) {
1268 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1269 $res = $foreign_clients_db->select_dbentry($sql);
1270 if (keys(%$res) > 0) {
1271 my $hostname = $res->{1}->{'hostname'};
1272 my ($host_ip, $host_port) = split(/:/, $hostname);
1273 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1274 my $regserver = $res->{1}->{'regserver'};
1275 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1276 my $res = $known_server_db->select_dbentry($sql);
1277 if (keys(%$res) > 0) {
1278 my $regserver_key = $res->{1}->{'hostkey'};
1279 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1280 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1281 if ($source eq "GOSA") {
1282 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1283 }
1284 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1285 }
1286 $done = 1;
1287 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1288 } else {
1289 $not_found_in_foreign_clients_db = 1;
1290 }
1291 }
1293 # target is a server address -> forward to server
1294 if (not $done) {
1295 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1296 $res = $known_server_db->select_dbentry($sql);
1297 if (keys(%$res) > 0) {
1298 my $hostkey = $res->{1}->{'hostkey'};
1300 if ($source eq "GOSA") {
1301 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1302 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1304 }
1306 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1307 $done = 1;
1308 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1309 } else {
1310 $not_found_in_known_server_db = 1;
1311 }
1312 }
1315 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1316 if ( $not_found_in_foreign_clients_db
1317 && $not_found_in_known_server_db
1318 && $not_found_in_known_clients_db) {
1319 &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);
1320 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1321 $module = "GosaPackages";
1322 }
1323 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1324 primkey=>[],
1325 headertag=>$header,
1326 targettag=>$target,
1327 xmlmessage=>&encode_base64($msg),
1328 timestamp=>&get_time,
1329 module=>$module,
1330 sessionid=>$session_id,
1331 } );
1332 $done = 1;
1333 }
1336 if (not $done) {
1337 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1338 if ($source eq "GOSA") {
1339 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1340 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1342 my $session_reference = $kernel->ID_id_to_session($session_id);
1343 if( defined $session_reference ) {
1344 $heap = $session_reference->get_heap();
1345 }
1346 if(exists $heap->{'client'}) {
1347 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1348 $heap->{'client'}->put($error_msg);
1349 }
1350 }
1351 }
1353 }
1355 return;
1356 }
1359 sub next_task {
1360 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1361 my $running_task = POE::Wheel::Run->new(
1362 Program => sub { process_task($session, $heap, $task) },
1363 StdioFilter => POE::Filter::Reference->new(),
1364 StdoutEvent => "task_result",
1365 StderrEvent => "task_debug",
1366 CloseEvent => "task_done",
1367 );
1368 $heap->{task}->{ $running_task->ID } = $running_task;
1369 }
1371 sub handle_task_result {
1372 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1373 my $client_answer = $result->{'answer'};
1374 if( $client_answer =~ s/session_id=(\d+)$// ) {
1375 my $session_id = $1;
1376 if( defined $session_id ) {
1377 my $session_reference = $kernel->ID_id_to_session($session_id);
1378 if( defined $session_reference ) {
1379 $heap = $session_reference->get_heap();
1380 }
1381 }
1383 if(exists $heap->{'client'}) {
1384 $heap->{'client'}->put($client_answer);
1385 }
1386 }
1387 $kernel->sig(CHLD => "child_reap");
1388 }
1390 sub handle_task_debug {
1391 my $result = $_[ARG0];
1392 print STDERR "$result\n";
1393 }
1395 sub handle_task_done {
1396 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1397 delete $heap->{task}->{$task_id};
1398 }
1400 sub process_task {
1401 no strict "refs";
1402 #CHECK: Not @_[...]?
1403 my ($session, $heap, $task) = @_;
1404 my $error = 0;
1405 my $answer_l;
1406 my ($answer_header, @answer_target_l, $answer_source);
1407 my $client_answer = "";
1409 # prepare all variables needed to process message
1410 #my $msg = $task->{'xmlmessage'};
1411 my $msg = &decode_base64($task->{'xmlmessage'});
1412 my $incoming_id = $task->{'id'};
1413 my $module = $task->{'module'};
1414 my $header = $task->{'headertag'};
1415 my $session_id = $task->{'sessionid'};
1416 my $msg_hash;
1417 eval {
1418 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1419 };
1420 daemon_log("ERROR: XML failure '$@'") if ($@);
1421 my $source = @{$msg_hash->{'source'}}[0];
1423 # set timestamp of incoming client uptodate, so client will not
1424 # be deleted from known_clients because of expiration
1425 my $act_time = &get_time();
1426 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1427 my $res = $known_clients_db->exec_statement($sql);
1429 ######################
1430 # process incoming msg
1431 if( $error == 0) {
1432 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1433 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1434 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1436 if ( 0 < @{$answer_l} ) {
1437 my $answer_str = join("\n", @{$answer_l});
1438 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1439 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1440 }
1441 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1442 } else {
1443 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1444 }
1446 }
1447 if( !$answer_l ) { $error++ };
1449 ########
1450 # answer
1451 if( $error == 0 ) {
1453 foreach my $answer ( @{$answer_l} ) {
1454 # check outgoing msg to xml validity
1455 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1456 if( not defined $answer_hash ) { next; }
1458 $answer_header = @{$answer_hash->{'header'}}[0];
1459 @answer_target_l = @{$answer_hash->{'target'}};
1460 $answer_source = @{$answer_hash->{'source'}}[0];
1462 # deliver msg to all targets
1463 foreach my $answer_target ( @answer_target_l ) {
1465 # targets of msg are all gosa-si-clients in known_clients_db
1466 if( $answer_target eq "*" ) {
1467 # answer is for all clients
1468 my $sql_statement= "SELECT * FROM known_clients";
1469 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1470 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1471 my $host_name = $hit->{hostname};
1472 my $host_key = $hit->{hostkey};
1473 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1474 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1475 }
1476 }
1478 # targets of msg are all gosa-si-server in known_server_db
1479 elsif( $answer_target eq "KNOWN_SERVER" ) {
1480 # answer is for all server in known_server
1481 my $sql_statement= "SELECT * FROM $known_server_tn";
1482 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1483 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1484 my $host_name = $hit->{hostname};
1485 my $host_key = $hit->{hostkey};
1486 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1487 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1488 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1489 }
1490 }
1492 # target of msg is GOsa
1493 elsif( $answer_target eq "GOSA" ) {
1494 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1495 my $add_on = "";
1496 if( defined $session_id ) {
1497 $add_on = ".session_id=$session_id";
1498 }
1499 # answer is for GOSA and has to returned to connected client
1500 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1501 $client_answer = $gosa_answer.$add_on;
1502 }
1504 # target of msg is job queue at this host
1505 elsif( $answer_target eq "JOBDB") {
1506 $answer =~ /<header>(\S+)<\/header>/;
1507 my $header;
1508 if( defined $1 ) { $header = $1; }
1509 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1510 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1511 }
1513 # Target of msg is a mac address
1514 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 ) {
1515 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1517 # Looking for macaddress in known_clients
1518 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1519 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1520 my $found_ip_flag = 0;
1521 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1522 my $host_name = $hit->{hostname};
1523 my $host_key = $hit->{hostkey};
1524 $answer =~ s/$answer_target/$host_name/g;
1525 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1526 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1527 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1528 $found_ip_flag++ ;
1529 }
1531 # Looking for macaddress in foreign_clients
1532 if ($found_ip_flag == 0) {
1533 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1534 my $res = $foreign_clients_db->select_dbentry($sql);
1535 while( my ($hit_num, $hit) = each %{ $res } ) {
1536 my $host_name = $hit->{hostname};
1537 my $reg_server = $hit->{regserver};
1538 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1540 # Fetch key for reg_server
1541 my $reg_server_key;
1542 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1543 my $res = $known_server_db->select_dbentry($sql);
1544 if (exists $res->{1}) {
1545 $reg_server_key = $res->{1}->{'hostkey'};
1546 } else {
1547 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1548 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1549 $reg_server_key = undef;
1550 }
1552 # Send answer to server where client is registered
1553 if (defined $reg_server_key) {
1554 $answer =~ s/$answer_target/$host_name/g;
1555 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1556 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1557 $found_ip_flag++ ;
1558 }
1559 }
1560 }
1562 # No mac to ip matching found
1563 if( $found_ip_flag == 0) {
1564 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1565 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1566 }
1568 # Answer is for one specific host
1569 } else {
1570 # get encrypt_key
1571 my $encrypt_key = &get_encrypt_key($answer_target);
1572 if( not defined $encrypt_key ) {
1573 # unknown target
1574 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1575 next;
1576 }
1577 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1578 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1579 }
1580 }
1581 }
1582 }
1584 my $filter = POE::Filter::Reference->new();
1585 my %result = (
1586 status => "seems ok to me",
1587 answer => $client_answer,
1588 );
1590 my $output = $filter->put( [ \%result ] );
1591 print @$output;
1594 }
1596 sub session_start {
1597 my ($kernel) = $_[KERNEL];
1598 $global_kernel = $kernel;
1599 $kernel->yield('register_at_foreign_servers');
1600 $kernel->yield('create_fai_server_db', $fai_server_tn );
1601 $kernel->yield('create_fai_release_db', $fai_release_tn );
1602 $kernel->yield('watch_for_next_tasks');
1603 $kernel->sig(USR1 => "sig_handler");
1604 $kernel->sig(USR2 => "recreate_packages_db");
1605 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1606 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1607 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1608 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1609 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1610 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1611 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1613 # Start opsi check
1614 if ($opsi_enabled eq "true") {
1615 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1616 }
1618 }
1621 sub watch_for_done_jobs {
1622 #CHECK: $heap for what?
1623 my ($kernel,$heap) = @_[KERNEL, HEAP];
1625 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1626 my $res = $job_db->select_dbentry( $sql_statement );
1628 while( my ($id, $hit) = each %{$res} ) {
1629 my $jobdb_id = $hit->{id};
1630 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1631 my $res = $job_db->del_dbentry($sql_statement);
1632 }
1634 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1635 }
1638 sub watch_for_opsi_jobs {
1639 my ($kernel) = $_[KERNEL];
1641 # 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
1642 # opsi install job is to parse the xml message. There is still the correct header.
1643 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1644 my $res = $job_db->select_dbentry( $sql_statement );
1646 # Ask OPSI for an update of the running jobs
1647 while (my ($id, $hit) = each %$res ) {
1648 # Determine current parameters of the job
1649 my $hostId = $hit->{'plainname'};
1650 my $macaddress = $hit->{'macaddress'};
1651 my $progress = $hit->{'progress'};
1653 my $result= {};
1655 # For hosts, only return the products that are or get installed
1656 my $callobj;
1657 $callobj = {
1658 method => 'getProductStates_hash',
1659 params => [ $hostId ],
1660 id => 1,
1661 };
1663 my $hres = $opsi_client->call($opsi_url, $callobj);
1664 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1665 if (not &check_opsi_res($hres)) {
1666 my $htmp= $hres->result->{$hostId};
1668 # Check state != not_installed or action == setup -> load and add
1669 my $products= 0;
1670 my $installed= 0;
1671 my $installing = 0;
1672 my $error= 0;
1673 my @installed_list;
1674 my @error_list;
1675 my $act_status = "none";
1676 foreach my $product (@{$htmp}){
1678 if ($product->{'installationStatus'} ne "not_installed" or
1679 $product->{'actionRequest'} eq "setup"){
1681 # Increase number of products for this host
1682 $products++;
1684 if ($product->{'installationStatus'} eq "failed"){
1685 $result->{$product->{'productId'}}= "error";
1686 unshift(@error_list, $product->{'productId'});
1687 $error++;
1688 }
1689 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1690 $result->{$product->{'productId'}}= "installed";
1691 unshift(@installed_list, $product->{'productId'});
1692 $installed++;
1693 }
1694 if ($product->{'installationStatus'} eq "installing"){
1695 $result->{$product->{'productId'}}= "installing";
1696 $installing++;
1697 $act_status = "installing - ".$product->{'productId'};
1698 }
1699 }
1700 }
1702 # Estimate "rough" progress, avoid division by zero
1703 if ($products == 0) {
1704 $result->{'progress'}= 0;
1705 } else {
1706 $result->{'progress'}= int($installed * 100 / $products);
1707 }
1709 # Set updates in job queue
1710 if ((not $error) && (not $installing) && ($installed)) {
1711 $act_status = "installed - ".join(", ", @installed_list);
1712 }
1713 if ($error) {
1714 $act_status = "error - ".join(", ", @error_list);
1715 }
1716 if ($progress ne $result->{'progress'} ) {
1717 # Updating progress and result
1718 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1719 my $update_res = $job_db->update_dbentry($update_statement);
1720 }
1721 if ($progress eq 100) {
1722 # Updateing status
1723 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1724 if ($error) {
1725 $done_statement .= "status='error'";
1726 } else {
1727 $done_statement .= "status='done'";
1728 }
1729 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1730 my $done_res = $job_db->update_dbentry($done_statement);
1731 }
1734 }
1735 }
1737 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1738 }
1741 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1742 sub watch_for_modified_jobs {
1743 my ($kernel,$heap) = @_[KERNEL, HEAP];
1745 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1746 my $res = $job_db->select_dbentry( $sql_statement );
1748 # if db contains no jobs which should be update, do nothing
1749 if (keys %$res != 0) {
1751 if ($job_synchronization eq "true") {
1752 # make out of the db result a gosa-si message
1753 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1755 # update all other SI-server
1756 &inform_all_other_si_server($update_msg);
1757 }
1759 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1760 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1761 $res = $job_db->update_dbentry($sql_statement);
1762 }
1764 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1765 }
1768 sub watch_for_new_jobs {
1769 if($watch_for_new_jobs_in_progress == 0) {
1770 $watch_for_new_jobs_in_progress = 1;
1771 my ($kernel,$heap) = @_[KERNEL, HEAP];
1773 # check gosa job quaeue for jobs with executable timestamp
1774 my $timestamp = &get_time();
1775 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1776 my $res = $job_db->exec_statement( $sql_statement );
1778 # Merge all new jobs that would do the same actions
1779 my @drops;
1780 my $hits;
1781 foreach my $hit (reverse @{$res} ) {
1782 my $macaddress= lc @{$hit}[8];
1783 my $headertag= @{$hit}[5];
1784 if(
1785 defined($hits->{$macaddress}) &&
1786 defined($hits->{$macaddress}->{$headertag}) &&
1787 defined($hits->{$macaddress}->{$headertag}[0])
1788 ) {
1789 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1790 }
1791 $hits->{$macaddress}->{$headertag}= $hit;
1792 }
1794 # Delete new jobs with a matching job in state 'processing'
1795 foreach my $macaddress (keys %{$hits}) {
1796 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1797 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1798 if(defined($jobdb_id)) {
1799 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1800 my $res = $job_db->exec_statement( $sql_statement );
1801 foreach my $hit (@{$res}) {
1802 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1803 }
1804 } else {
1805 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1806 }
1807 }
1808 }
1810 # Commit deletion
1811 $job_db->exec_statementlist(\@drops);
1813 # Look for new jobs that could be executed
1814 foreach my $macaddress (keys %{$hits}) {
1816 # Look if there is an executing job
1817 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1818 my $res = $job_db->exec_statement( $sql_statement );
1820 # Skip new jobs for host if there is a processing job
1821 if(defined($res) and defined @{$res}[0]) {
1822 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1823 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1824 if(@{$row}[5] eq 'trigger_action_reinstall') {
1825 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1826 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1827 if(defined($res_2) and defined @{$res_2}[0]) {
1828 # Set status from goto-activation to 'waiting' and update timestamp
1829 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1830 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1831 }
1832 }
1833 next;
1834 }
1836 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1837 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1838 if(defined($jobdb_id)) {
1839 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1841 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1842 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1843 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1845 # expect macaddress is unique!!!!!!
1846 my $target = $res_hash->{1}->{hostname};
1848 # change header
1849 $job_msg =~ s/<header>job_/<header>gosa_/;
1851 # add sqlite_id
1852 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1854 $job_msg =~ /<header>(\S+)<\/header>/;
1855 my $header = $1 ;
1856 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1858 # update status in job queue to ...
1859 # ... 'processing', for jobs: 'reinstall', 'update'
1860 if (($header =~ /gosa_trigger_action_reinstall/)
1861 || ($header =~ /gosa_trigger_activate_new/)
1862 || ($header =~ /gosa_trigger_action_update/)) {
1863 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1864 my $dbres = $job_db->update_dbentry($sql_statement);
1865 }
1867 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1868 else {
1869 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1870 my $dbres = $job_db->update_dbentry($sql_statement);
1871 }
1874 # We don't want parallel processing
1875 last;
1876 }
1877 }
1878 }
1880 $watch_for_new_jobs_in_progress = 0;
1881 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1882 }
1883 }
1886 sub watch_for_new_messages {
1887 my ($kernel,$heap) = @_[KERNEL, HEAP];
1888 my @coll_user_msg; # collection list of outgoing messages
1890 # check messaging_db for new incoming messages with executable timestamp
1891 my $timestamp = &get_time();
1892 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1893 my $res = $messaging_db->exec_statement( $sql_statement );
1894 foreach my $hit (@{$res}) {
1896 # create outgoing messages
1897 my $message_to = @{$hit}[3];
1898 # translate message_to to plain login name
1899 my @message_to_l = split(/,/, $message_to);
1900 my %receiver_h;
1901 foreach my $receiver (@message_to_l) {
1902 if ($receiver =~ /^u_([\s\S]*)$/) {
1903 $receiver_h{$1} = 0;
1904 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1905 my $group_name = $1;
1906 # fetch all group members from ldap and add them to receiver hash
1907 my $ldap_handle = &get_ldap_handle();
1908 if (defined $ldap_handle) {
1909 my $mesg = $ldap_handle->search(
1910 base => $ldap_base,
1911 scope => 'sub',
1912 attrs => ['memberUid'],
1913 filter => "cn=$group_name",
1914 );
1915 if ($mesg->count) {
1916 my @entries = $mesg->entries;
1917 foreach my $entry (@entries) {
1918 my @receivers= $entry->get_value("memberUid");
1919 foreach my $receiver (@receivers) {
1920 $receiver_h{$receiver} = 0;
1921 }
1922 }
1923 }
1924 # translating errors ?
1925 if ($mesg->code) {
1926 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1927 }
1928 # ldap handle error ?
1929 } else {
1930 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1931 }
1932 } else {
1933 my $sbjct = &encode_base64(@{$hit}[1]);
1934 my $msg = &encode_base64(@{$hit}[7]);
1935 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1936 }
1937 }
1938 my @receiver_l = keys(%receiver_h);
1940 my $message_id = @{$hit}[0];
1942 #add each outgoing msg to messaging_db
1943 my $receiver;
1944 foreach $receiver (@receiver_l) {
1945 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1946 "VALUES ('".
1947 $message_id."', '". # id
1948 @{$hit}[1]."', '". # subject
1949 @{$hit}[2]."', '". # message_from
1950 $receiver."', '". # message_to
1951 "none"."', '". # flag
1952 "out"."', '". # direction
1953 @{$hit}[6]."', '". # delivery_time
1954 @{$hit}[7]."', '". # message
1955 $timestamp."'". # timestamp
1956 ")";
1957 &daemon_log("M DEBUG: $sql_statement", 1);
1958 my $res = $messaging_db->exec_statement($sql_statement);
1959 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1960 }
1962 # set incoming message to flag d=deliverd
1963 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1964 &daemon_log("M DEBUG: $sql_statement", 7);
1965 $res = $messaging_db->update_dbentry($sql_statement);
1966 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1967 }
1969 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1970 return;
1971 }
1973 sub watch_for_delivery_messages {
1974 my ($kernel, $heap) = @_[KERNEL, HEAP];
1976 # select outgoing messages
1977 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1978 #&daemon_log("0 DEBUG: $sql", 7);
1979 my $res = $messaging_db->exec_statement( $sql_statement );
1981 # build out msg for each usr
1982 foreach my $hit (@{$res}) {
1983 my $receiver = @{$hit}[3];
1984 my $msg_id = @{$hit}[0];
1985 my $subject = @{$hit}[1];
1986 my $message = @{$hit}[7];
1988 # resolve usr -> host where usr is logged in
1989 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1990 #&daemon_log("0 DEBUG: $sql", 7);
1991 my $res = $login_users_db->exec_statement($sql);
1993 # receiver is logged in nowhere
1994 if (not ref(@$res[0]) eq "ARRAY") { next; }
1996 # receiver ist logged in at a client registered at local server
1997 my $send_succeed = 0;
1998 foreach my $hit (@$res) {
1999 my $receiver_host = @$hit[0];
2000 my $delivered2host = 0;
2001 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2003 # Looking for host in know_clients_db
2004 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2005 my $res = $known_clients_db->exec_statement($sql);
2007 # Host is known in known_clients_db
2008 if (ref(@$res[0]) eq "ARRAY") {
2009 my $receiver_key = @{@{$res}[0]}[2];
2010 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2011 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2012 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2013 if ($error == 0 ) {
2014 $send_succeed++ ;
2015 $delivered2host++ ;
2016 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2017 } else {
2018 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2019 }
2020 }
2022 # Message already send, do not need to do anything more, otherwise ...
2023 if ($delivered2host) { next;}
2025 # ...looking for host in foreign_clients_db
2026 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2027 $res = $foreign_clients_db->exec_statement($sql);
2029 # Host is known in foreign_clients_db
2030 if (ref(@$res[0]) eq "ARRAY") {
2031 my $registration_server = @{@{$res}[0]}[2];
2033 # Fetch encryption key for registration server
2034 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2035 my $res = $known_server_db->exec_statement($sql);
2036 if (ref(@$res[0]) eq "ARRAY") {
2037 my $registration_server_key = @{@{$res}[0]}[3];
2038 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2039 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2040 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2041 if ($error == 0 ) {
2042 $send_succeed++ ;
2043 $delivered2host++ ;
2044 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2045 } else {
2046 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2047 }
2049 } else {
2050 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2051 "registrated at server '$registration_server', ".
2052 "but no data available in known_server_db ", 1);
2053 }
2054 }
2056 if (not $delivered2host) {
2057 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2058 }
2059 }
2061 if ($send_succeed) {
2062 # set outgoing msg at db to deliverd
2063 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2064 my $res = $messaging_db->exec_statement($sql);
2065 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2066 } else {
2067 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2068 }
2069 }
2071 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2072 return;
2073 }
2076 sub watch_for_done_messages {
2077 my ($kernel,$heap) = @_[KERNEL, HEAP];
2079 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2080 #&daemon_log("0 DEBUG: $sql", 7);
2081 my $res = $messaging_db->exec_statement($sql);
2083 foreach my $hit (@{$res}) {
2084 my $msg_id = @{$hit}[0];
2086 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2087 #&daemon_log("0 DEBUG: $sql", 7);
2088 my $res = $messaging_db->exec_statement($sql);
2090 # not all usr msgs have been seen till now
2091 if ( ref(@$res[0]) eq "ARRAY") { next; }
2093 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2094 #&daemon_log("0 DEBUG: $sql", 7);
2095 $res = $messaging_db->exec_statement($sql);
2097 }
2099 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2100 return;
2101 }
2104 sub watch_for_old_known_clients {
2105 my ($kernel,$heap) = @_[KERNEL, HEAP];
2107 my $sql_statement = "SELECT * FROM $known_clients_tn";
2108 my $res = $known_clients_db->select_dbentry( $sql_statement );
2110 my $act_time = int(&get_time());
2112 while ( my ($hit_num, $hit) = each %$res) {
2113 my $expired_timestamp = int($hit->{'timestamp'});
2114 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2115 my $dt = DateTime->new( year => $1,
2116 month => $2,
2117 day => $3,
2118 hour => $4,
2119 minute => $5,
2120 second => $6,
2121 );
2123 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2124 $expired_timestamp = $dt->ymd('').$dt->hms('');
2125 if ($act_time > $expired_timestamp) {
2126 my $hostname = $hit->{'hostname'};
2127 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2128 my $del_res = $known_clients_db->exec_statement($del_sql);
2130 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2131 }
2133 }
2135 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2136 }
2139 sub watch_for_next_tasks {
2140 my ($kernel,$heap) = @_[KERNEL, HEAP];
2142 my $sql = "SELECT * FROM $incoming_tn";
2143 my $res = $incoming_db->select_dbentry($sql);
2145 while ( my ($hit_num, $hit) = each %$res) {
2146 my $headertag = $hit->{'headertag'};
2147 if ($headertag =~ /^answer_(\d+)/) {
2148 # do not start processing, this message is for a still running POE::Wheel
2149 next;
2150 }
2151 my $message_id = $hit->{'id'};
2152 my $session_id = $hit->{'sessionid'};
2153 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2154 $kernel->yield('next_task', $hit);
2156 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2157 my $res = $incoming_db->exec_statement($sql);
2158 }
2160 $kernel->delay_set('watch_for_next_tasks', 1);
2161 }
2164 sub get_ldap_handle {
2165 my ($session_id) = @_;
2166 my $heap;
2167 my $ldap_handle;
2169 if (not defined $session_id ) { $session_id = 0 };
2170 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2172 if ($session_id == 0) {
2173 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2174 $ldap_handle = Net::LDAP->new( $ldap_uri );
2175 if (defined $ldap_handle) {
2176 $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!");
2177 } else {
2178 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2179 }
2181 } else {
2182 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2183 if( defined $session_reference ) {
2184 $heap = $session_reference->get_heap();
2185 }
2187 if (not defined $heap) {
2188 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2189 return;
2190 }
2192 # TODO: This "if" is nonsense, because it doesn't prove that the
2193 # used handle is still valid - or if we've to reconnect...
2194 #if (not exists $heap->{ldap_handle}) {
2195 $ldap_handle = Net::LDAP->new( $ldap_uri );
2196 $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!");
2197 $heap->{ldap_handle} = $ldap_handle;
2198 #}
2199 }
2200 return $ldap_handle;
2201 }
2204 sub change_fai_state {
2205 my ($st, $targets, $session_id) = @_;
2206 $session_id = 0 if not defined $session_id;
2207 # Set FAI state to localboot
2208 my %mapActions= (
2209 reboot => '',
2210 update => 'softupdate',
2211 localboot => 'localboot',
2212 reinstall => 'install',
2213 rescan => '',
2214 wake => '',
2215 memcheck => 'memcheck',
2216 sysinfo => 'sysinfo',
2217 install => 'install',
2218 );
2220 # Return if this is unknown
2221 if (!exists $mapActions{ $st }){
2222 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2223 return;
2224 }
2226 my $state= $mapActions{ $st };
2228 my $ldap_handle = &get_ldap_handle($session_id);
2229 if( defined($ldap_handle) ) {
2231 # Build search filter for hosts
2232 my $search= "(&(objectClass=GOhard)";
2233 foreach (@{$targets}){
2234 $search.= "(macAddress=$_)";
2235 }
2236 $search.= ")";
2238 # If there's any host inside of the search string, procress them
2239 if (!($search =~ /macAddress/)){
2240 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2241 return;
2242 }
2244 # Perform search for Unit Tag
2245 my $mesg = $ldap_handle->search(
2246 base => $ldap_base,
2247 scope => 'sub',
2248 attrs => ['dn', 'FAIstate', 'objectClass'],
2249 filter => "$search"
2250 );
2252 if ($mesg->count) {
2253 my @entries = $mesg->entries;
2254 if (0 == @entries) {
2255 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2256 }
2258 foreach my $entry (@entries) {
2259 # Only modify entry if it is not set to '$state'
2260 if ($entry->get_value("FAIstate") ne "$state"){
2261 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2262 my $result;
2263 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2264 if (exists $tmp{'FAIobject'}){
2265 if ($state eq ''){
2266 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2267 } else {
2268 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2269 }
2270 } elsif ($state ne ''){
2271 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2272 }
2274 # Errors?
2275 if ($result->code){
2276 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2277 }
2278 } else {
2279 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2280 }
2281 }
2282 } else {
2283 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2284 }
2286 # if no ldap handle defined
2287 } else {
2288 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2289 }
2291 return;
2292 }
2295 sub change_goto_state {
2296 my ($st, $targets, $session_id) = @_;
2297 $session_id = 0 if not defined $session_id;
2299 # Switch on or off?
2300 my $state= $st eq 'active' ? 'active': 'locked';
2302 my $ldap_handle = &get_ldap_handle($session_id);
2303 if( defined($ldap_handle) ) {
2305 # Build search filter for hosts
2306 my $search= "(&(objectClass=GOhard)";
2307 foreach (@{$targets}){
2308 $search.= "(macAddress=$_)";
2309 }
2310 $search.= ")";
2312 # If there's any host inside of the search string, procress them
2313 if (!($search =~ /macAddress/)){
2314 return;
2315 }
2317 # Perform search for Unit Tag
2318 my $mesg = $ldap_handle->search(
2319 base => $ldap_base,
2320 scope => 'sub',
2321 attrs => ['dn', 'gotoMode'],
2322 filter => "$search"
2323 );
2325 if ($mesg->count) {
2326 my @entries = $mesg->entries;
2327 foreach my $entry (@entries) {
2329 # Only modify entry if it is not set to '$state'
2330 if ($entry->get_value("gotoMode") ne $state){
2332 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2333 my $result;
2334 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2336 # Errors?
2337 if ($result->code){
2338 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2339 }
2341 }
2342 }
2343 } else {
2344 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2345 }
2347 }
2348 }
2351 sub run_recreate_packages_db {
2352 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2353 my $session_id = $session->ID;
2354 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2355 $kernel->yield('create_fai_release_db', $fai_release_tn);
2356 $kernel->yield('create_fai_server_db', $fai_server_tn);
2357 return;
2358 }
2361 sub run_create_fai_server_db {
2362 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2363 my $session_id = $session->ID;
2364 my $task = POE::Wheel::Run->new(
2365 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2366 StdoutEvent => "session_run_result",
2367 StderrEvent => "session_run_debug",
2368 CloseEvent => "session_run_done",
2369 );
2371 $heap->{task}->{ $task->ID } = $task;
2372 return;
2373 }
2376 sub create_fai_server_db {
2377 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2378 my $result;
2380 if (not defined $session_id) { $session_id = 0; }
2381 my $ldap_handle = &get_ldap_handle();
2382 if(defined($ldap_handle)) {
2383 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2384 my $mesg= $ldap_handle->search(
2385 base => $ldap_base,
2386 scope => 'sub',
2387 attrs => ['FAIrepository', 'gosaUnitTag'],
2388 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2389 );
2390 if($mesg->{'resultCode'} == 0 &&
2391 $mesg->count != 0) {
2392 foreach my $entry (@{$mesg->{entries}}) {
2393 if($entry->exists('FAIrepository')) {
2394 # Add an entry for each Repository configured for server
2395 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2396 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2397 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2398 $result= $fai_server_db->add_dbentry( {
2399 table => $table_name,
2400 primkey => ['server', 'fai_release', 'tag'],
2401 server => $tmp_url,
2402 fai_release => $tmp_release,
2403 sections => $tmp_sections,
2404 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2405 } );
2406 }
2407 }
2408 }
2409 }
2410 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2412 # TODO: Find a way to post the 'create_packages_list_db' event
2413 if(not defined($dont_create_packages_list)) {
2414 &create_packages_list_db(undef, undef, $session_id);
2415 }
2416 }
2418 $ldap_handle->disconnect;
2419 return $result;
2420 }
2423 sub run_create_fai_release_db {
2424 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2425 my $session_id = $session->ID;
2426 my $task = POE::Wheel::Run->new(
2427 Program => sub { &create_fai_release_db($table_name, $session_id) },
2428 StdoutEvent => "session_run_result",
2429 StderrEvent => "session_run_debug",
2430 CloseEvent => "session_run_done",
2431 );
2433 $heap->{task}->{ $task->ID } = $task;
2434 return;
2435 }
2438 sub create_fai_release_db {
2439 my ($table_name, $session_id) = @_;
2440 my $result;
2442 # used for logging
2443 if (not defined $session_id) { $session_id = 0; }
2445 my $ldap_handle = &get_ldap_handle();
2446 if(defined($ldap_handle)) {
2447 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2448 my $mesg= $ldap_handle->search(
2449 base => $ldap_base,
2450 scope => 'sub',
2451 attrs => [],
2452 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2453 );
2454 if($mesg->{'resultCode'} == 0 &&
2455 $mesg->count != 0) {
2456 # Walk through all possible FAI container ou's
2457 my @sql_list;
2458 my $timestamp= &get_time();
2459 foreach my $ou (@{$mesg->{entries}}) {
2460 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2461 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2462 my @tmp_array=get_fai_release_entries($tmp_classes);
2463 if(@tmp_array) {
2464 foreach my $entry (@tmp_array) {
2465 if(defined($entry) && ref($entry) eq 'HASH') {
2466 my $sql=
2467 "INSERT INTO $table_name "
2468 ."(timestamp, fai_release, class, type, state) VALUES ("
2469 .$timestamp.","
2470 ."'".$entry->{'release'}."',"
2471 ."'".$entry->{'class'}."',"
2472 ."'".$entry->{'type'}."',"
2473 ."'".$entry->{'state'}."')";
2474 push @sql_list, $sql;
2475 }
2476 }
2477 }
2478 }
2479 }
2481 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2482 if(@sql_list) {
2483 unshift @sql_list, "DELETE FROM $table_name";
2484 $fai_release_db->exec_statementlist(\@sql_list);
2485 }
2486 daemon_log("$session_id DEBUG: Done with inserting",7);
2487 }
2488 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2489 }
2490 $ldap_handle->disconnect;
2491 return $result;
2492 }
2494 sub get_fai_types {
2495 my $tmp_classes = shift || return undef;
2496 my @result;
2498 foreach my $type(keys %{$tmp_classes}) {
2499 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2500 my $entry = {
2501 type => $type,
2502 state => $tmp_classes->{$type}[0],
2503 };
2504 push @result, $entry;
2505 }
2506 }
2508 return @result;
2509 }
2511 sub get_fai_state {
2512 my $result = "";
2513 my $tmp_classes = shift || return $result;
2515 foreach my $type(keys %{$tmp_classes}) {
2516 if(defined($tmp_classes->{$type}[0])) {
2517 $result = $tmp_classes->{$type}[0];
2519 # State is equal for all types in class
2520 last;
2521 }
2522 }
2524 return $result;
2525 }
2527 sub resolve_fai_classes {
2528 my ($fai_base, $ldap_handle, $session_id) = @_;
2529 if (not defined $session_id) { $session_id = 0; }
2530 my $result;
2531 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2532 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2533 my $fai_classes;
2535 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2536 my $mesg= $ldap_handle->search(
2537 base => $fai_base,
2538 scope => 'sub',
2539 attrs => ['cn','objectClass','FAIstate'],
2540 filter => $fai_filter,
2541 );
2542 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2544 if($mesg->{'resultCode'} == 0 &&
2545 $mesg->count != 0) {
2546 foreach my $entry (@{$mesg->{entries}}) {
2547 if($entry->exists('cn')) {
2548 my $tmp_dn= $entry->dn();
2550 # Skip classname and ou dn parts for class
2551 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2553 # Skip classes without releases
2554 if((!defined($tmp_release)) || length($tmp_release)==0) {
2555 next;
2556 }
2558 my $tmp_cn= $entry->get_value('cn');
2559 my $tmp_state= $entry->get_value('FAIstate');
2561 my $tmp_type;
2562 # Get FAI type
2563 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2564 if(grep $_ eq $oclass, @possible_fai_classes) {
2565 $tmp_type= $oclass;
2566 last;
2567 }
2568 }
2570 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2571 # A Subrelease
2572 my @sub_releases = split(/,/, $tmp_release);
2574 # Walk through subreleases and build hash tree
2575 my $hash;
2576 while(my $tmp_sub_release = pop @sub_releases) {
2577 $hash .= "\{'$tmp_sub_release'\}->";
2578 }
2579 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2580 } else {
2581 # A branch, no subrelease
2582 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2583 }
2584 } elsif (!$entry->exists('cn')) {
2585 my $tmp_dn= $entry->dn();
2586 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2588 # Skip classes without releases
2589 if((!defined($tmp_release)) || length($tmp_release)==0) {
2590 next;
2591 }
2593 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2594 # A Subrelease
2595 my @sub_releases= split(/,/, $tmp_release);
2597 # Walk through subreleases and build hash tree
2598 my $hash;
2599 while(my $tmp_sub_release = pop @sub_releases) {
2600 $hash .= "\{'$tmp_sub_release'\}->";
2601 }
2602 # Remove the last two characters
2603 chop($hash);
2604 chop($hash);
2606 eval('$fai_classes->'.$hash.'= {}');
2607 } else {
2608 # A branch, no subrelease
2609 if(!exists($fai_classes->{$tmp_release})) {
2610 $fai_classes->{$tmp_release} = {};
2611 }
2612 }
2613 }
2614 }
2616 # The hash is complete, now we can honor the copy-on-write based missing entries
2617 foreach my $release (keys %$fai_classes) {
2618 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2619 }
2620 }
2621 return $result;
2622 }
2624 sub apply_fai_inheritance {
2625 my $fai_classes = shift || return {};
2626 my $tmp_classes;
2628 # Get the classes from the branch
2629 foreach my $class (keys %{$fai_classes}) {
2630 # Skip subreleases
2631 if($class =~ /^ou=.*$/) {
2632 next;
2633 } else {
2634 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2635 }
2636 }
2638 # Apply to each subrelease
2639 foreach my $subrelease (keys %{$fai_classes}) {
2640 if($subrelease =~ /ou=/) {
2641 foreach my $tmp_class (keys %{$tmp_classes}) {
2642 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2643 $fai_classes->{$subrelease}->{$tmp_class} =
2644 deep_copy($tmp_classes->{$tmp_class});
2645 } else {
2646 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2647 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2648 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2649 deep_copy($tmp_classes->{$tmp_class}->{$type});
2650 }
2651 }
2652 }
2653 }
2654 }
2655 }
2657 # Find subreleases in deeper levels
2658 foreach my $subrelease (keys %{$fai_classes}) {
2659 if($subrelease =~ /ou=/) {
2660 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2661 if($subsubrelease =~ /ou=/) {
2662 apply_fai_inheritance($fai_classes->{$subrelease});
2663 }
2664 }
2665 }
2666 }
2668 return $fai_classes;
2669 }
2671 sub get_fai_release_entries {
2672 my $tmp_classes = shift || return;
2673 my $parent = shift || "";
2674 my @result = shift || ();
2676 foreach my $entry (keys %{$tmp_classes}) {
2677 if(defined($entry)) {
2678 if($entry =~ /^ou=.*$/) {
2679 my $release_name = $entry;
2680 $release_name =~ s/ou=//g;
2681 if(length($parent)>0) {
2682 $release_name = $parent."/".$release_name;
2683 }
2684 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2685 foreach my $bufentry(@bufentries) {
2686 push @result, $bufentry;
2687 }
2688 } else {
2689 my @types = get_fai_types($tmp_classes->{$entry});
2690 foreach my $type (@types) {
2691 push @result,
2692 {
2693 'class' => $entry,
2694 'type' => $type->{'type'},
2695 'release' => $parent,
2696 'state' => $type->{'state'},
2697 };
2698 }
2699 }
2700 }
2701 }
2703 return @result;
2704 }
2706 sub deep_copy {
2707 my $this = shift;
2708 if (not ref $this) {
2709 $this;
2710 } elsif (ref $this eq "ARRAY") {
2711 [map deep_copy($_), @$this];
2712 } elsif (ref $this eq "HASH") {
2713 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2714 } else { die "what type is $_?" }
2715 }
2718 sub session_run_result {
2719 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2720 $kernel->sig(CHLD => "child_reap");
2721 }
2723 sub session_run_debug {
2724 my $result = $_[ARG0];
2725 print STDERR "$result\n";
2726 }
2728 sub session_run_done {
2729 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2730 delete $heap->{task}->{$task_id};
2731 }
2734 sub create_sources_list {
2735 my $session_id = shift;
2736 my $ldap_handle = &main::get_ldap_handle;
2737 my $result="/tmp/gosa_si_tmp_sources_list";
2739 # Remove old file
2740 if(stat($result)) {
2741 unlink($result);
2742 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2743 }
2745 my $fh;
2746 open($fh, ">$result");
2747 if (not defined $fh) {
2748 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2749 return undef;
2750 }
2751 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2752 my $mesg=$ldap_handle->search(
2753 base => $main::ldap_server_dn,
2754 scope => 'base',
2755 attrs => 'FAIrepository',
2756 filter => 'objectClass=FAIrepositoryServer'
2757 );
2758 if($mesg->count) {
2759 foreach my $entry(@{$mesg->{'entries'}}) {
2760 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2761 my ($server, $tag, $release, $sections)= split /\|/, $value;
2762 my $line = "deb $server $release";
2763 $sections =~ s/,/ /g;
2764 $line.= " $sections";
2765 print $fh $line."\n";
2766 }
2767 }
2768 }
2769 } else {
2770 if (defined $main::ldap_server_dn){
2771 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2772 } else {
2773 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2774 }
2775 }
2776 close($fh);
2778 return $result;
2779 }
2782 sub run_create_packages_list_db {
2783 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2784 my $session_id = $session->ID;
2786 my $task = POE::Wheel::Run->new(
2787 Priority => +20,
2788 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2789 StdoutEvent => "session_run_result",
2790 StderrEvent => "session_run_debug",
2791 CloseEvent => "session_run_done",
2792 );
2793 $heap->{task}->{ $task->ID } = $task;
2794 }
2797 sub create_packages_list_db {
2798 my ($ldap_handle, $sources_file, $session_id) = @_;
2800 # it should not be possible to trigger a recreation of packages_list_db
2801 # while packages_list_db is under construction, so set flag packages_list_under_construction
2802 # which is tested befor recreation can be started
2803 if (-r $packages_list_under_construction) {
2804 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2805 return;
2806 } else {
2807 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2808 # set packages_list_under_construction to true
2809 system("touch $packages_list_under_construction");
2810 @packages_list_statements=();
2811 }
2813 if (not defined $session_id) { $session_id = 0; }
2814 if (not defined $ldap_handle) {
2815 $ldap_handle= &get_ldap_handle();
2817 if (not defined $ldap_handle) {
2818 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2819 unlink($packages_list_under_construction);
2820 return;
2821 }
2822 }
2823 if (not defined $sources_file) {
2824 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2825 $sources_file = &create_sources_list($session_id);
2826 }
2828 if (not defined $sources_file) {
2829 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2830 unlink($packages_list_under_construction);
2831 return;
2832 }
2834 my $line;
2836 open(CONFIG, "<$sources_file") or do {
2837 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2838 unlink($packages_list_under_construction);
2839 return;
2840 };
2842 # Read lines
2843 while ($line = <CONFIG>){
2844 # Unify
2845 chop($line);
2846 $line =~ s/^\s+//;
2847 $line =~ s/^\s+/ /;
2849 # Strip comments
2850 $line =~ s/#.*$//g;
2852 # Skip empty lines
2853 if ($line =~ /^\s*$/){
2854 next;
2855 }
2857 # Interpret deb line
2858 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2859 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2860 my $section;
2861 foreach $section (split(' ', $sections)){
2862 &parse_package_info( $baseurl, $dist, $section, $session_id );
2863 }
2864 }
2865 }
2867 close (CONFIG);
2869 if(keys(%repo_dirs)) {
2870 find(\&cleanup_and_extract, keys( %repo_dirs ));
2871 &main::strip_packages_list_statements();
2872 $packages_list_db->exec_statementlist(\@packages_list_statements);
2873 }
2874 unlink($packages_list_under_construction);
2875 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2876 return;
2877 }
2879 # This function should do some intensive task to minimize the db-traffic
2880 sub strip_packages_list_statements {
2881 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2882 my @new_statement_list=();
2883 my $hash;
2884 my $insert_hash;
2885 my $update_hash;
2886 my $delete_hash;
2887 my $known_packages_hash;
2888 my $local_timestamp=get_time();
2890 foreach my $existing_entry (@existing_entries) {
2891 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2892 }
2894 foreach my $statement (@packages_list_statements) {
2895 if($statement =~ /^INSERT/i) {
2896 # Assign the values from the insert statement
2897 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2898 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2899 if(exists($hash->{$distribution}->{$package}->{$version})) {
2900 # If section or description has changed, update the DB
2901 if(
2902 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2903 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2904 ) {
2905 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2906 } else {
2907 # package is already present in database. cache this knowledge for later use
2908 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2909 }
2910 } else {
2911 # Insert a non-existing entry to db
2912 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2913 }
2914 } elsif ($statement =~ /^UPDATE/i) {
2915 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2916 /^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;
2917 foreach my $distribution (keys %{$hash}) {
2918 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2919 # update the insertion hash to execute only one query per package (insert instead insert+update)
2920 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2921 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2922 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2923 my $section;
2924 my $description;
2925 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2926 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2927 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2928 }
2929 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2930 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2931 }
2932 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2933 }
2934 }
2935 }
2936 }
2937 }
2939 # Check for orphaned entries
2940 foreach my $existing_entry (@existing_entries) {
2941 my $distribution= @{$existing_entry}[0];
2942 my $package= @{$existing_entry}[1];
2943 my $version= @{$existing_entry}[2];
2944 my $section= @{$existing_entry}[3];
2946 if(
2947 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2948 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2949 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2950 ) {
2951 next;
2952 } else {
2953 # Insert entry to delete hash
2954 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2955 }
2956 }
2958 # unroll the insert hash
2959 foreach my $distribution (keys %{$insert_hash}) {
2960 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2961 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2962 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2963 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2964 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2965 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2966 ."'$local_timestamp')";
2967 }
2968 }
2969 }
2971 # unroll the update hash
2972 foreach my $distribution (keys %{$update_hash}) {
2973 foreach my $package (keys %{$update_hash->{$distribution}}) {
2974 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2975 my $set = "";
2976 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2977 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2978 }
2979 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2980 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2981 }
2982 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2983 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2984 }
2985 if(defined($set) and length($set) > 0) {
2986 $set .= "timestamp = '$local_timestamp'";
2987 } else {
2988 next;
2989 }
2990 push @new_statement_list,
2991 "UPDATE $main::packages_list_tn SET $set WHERE"
2992 ." distribution = '$distribution'"
2993 ." AND package = '$package'"
2994 ." AND version = '$version'";
2995 }
2996 }
2997 }
2999 # unroll the delete hash
3000 foreach my $distribution (keys %{$delete_hash}) {
3001 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3002 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3003 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3004 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3005 }
3006 }
3007 }
3009 @packages_list_statements = @new_statement_list;
3010 }
3013 sub parse_package_info {
3014 my ($baseurl, $dist, $section, $session_id)= @_;
3015 my ($package);
3016 if (not defined $session_id) { $session_id = 0; }
3017 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3018 $repo_dirs{ "${repo_path}/pool" } = 1;
3020 foreach $package ("Packages.gz"){
3021 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3022 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3023 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3024 }
3026 }
3029 sub get_package {
3030 my ($url, $dest, $session_id)= @_;
3031 if (not defined $session_id) { $session_id = 0; }
3033 my $tpath = dirname($dest);
3034 -d "$tpath" || mkpath "$tpath";
3036 # This is ugly, but I've no time to take a look at "how it works in perl"
3037 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3038 system("gunzip -cd '$dest' > '$dest.in'");
3039 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3040 unlink($dest);
3041 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3042 } else {
3043 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
3044 }
3045 return 0;
3046 }
3049 sub parse_package {
3050 my ($path, $dist, $srv_path, $session_id)= @_;
3051 if (not defined $session_id) { $session_id = 0;}
3052 my ($package, $version, $section, $description);
3053 my $PACKAGES;
3054 my $timestamp = &get_time();
3056 if(not stat("$path.in")) {
3057 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3058 return;
3059 }
3061 open($PACKAGES, "<$path.in");
3062 if(not defined($PACKAGES)) {
3063 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3064 return;
3065 }
3067 # Read lines
3068 while (<$PACKAGES>){
3069 my $line = $_;
3070 # Unify
3071 chop($line);
3073 # Use empty lines as a trigger
3074 if ($line =~ /^\s*$/){
3075 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3076 push(@packages_list_statements, $sql);
3077 $package = "none";
3078 $version = "none";
3079 $section = "none";
3080 $description = "none";
3081 next;
3082 }
3084 # Trigger for package name
3085 if ($line =~ /^Package:\s/){
3086 ($package)= ($line =~ /^Package: (.*)$/);
3087 next;
3088 }
3090 # Trigger for version
3091 if ($line =~ /^Version:\s/){
3092 ($version)= ($line =~ /^Version: (.*)$/);
3093 next;
3094 }
3096 # Trigger for description
3097 if ($line =~ /^Description:\s/){
3098 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3099 next;
3100 }
3102 # Trigger for section
3103 if ($line =~ /^Section:\s/){
3104 ($section)= ($line =~ /^Section: (.*)$/);
3105 next;
3106 }
3108 # Trigger for filename
3109 if ($line =~ /^Filename:\s/){
3110 my ($filename) = ($line =~ /^Filename: (.*)$/);
3111 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3112 next;
3113 }
3114 }
3116 close( $PACKAGES );
3117 unlink( "$path.in" );
3118 }
3121 sub store_fileinfo {
3122 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3124 my %fileinfo = (
3125 'package' => $package,
3126 'dist' => $dist,
3127 'version' => $vers,
3128 );
3130 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3131 }
3134 sub cleanup_and_extract {
3135 my $fileinfo = $repo_files{ $File::Find::name };
3137 if( defined $fileinfo ) {
3138 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3139 my $sql;
3140 my $package = $fileinfo->{ 'package' };
3141 my $newver = $fileinfo->{ 'version' };
3143 mkpath($dir);
3144 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3146 if( -f "$dir/DEBIAN/templates" ) {
3148 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3150 my $tmpl= ""; {
3151 local $/=undef;
3152 open FILE, "$dir/DEBIAN/templates";
3153 $tmpl = &encode_base64(<FILE>);
3154 close FILE;
3155 }
3156 rmtree("$dir/DEBIAN/templates");
3158 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3159 push @packages_list_statements, $sql;
3160 }
3161 }
3163 return;
3164 }
3167 sub register_at_foreign_servers {
3168 my ($kernel) = $_[KERNEL];
3170 # hole alle bekannten server aus known_server_db
3171 my $server_sql = "SELECT * FROM $known_server_tn";
3172 my $server_res = $known_server_db->exec_statement($server_sql);
3174 # no entries in known_server_db
3175 if (not ref(@$server_res[0]) eq "ARRAY") {
3176 # TODO
3177 }
3179 # detect already connected clients
3180 my $client_sql = "SELECT * FROM $known_clients_tn";
3181 my $client_res = $known_clients_db->exec_statement($client_sql);
3183 # send my server details to all other gosa-si-server within the network
3184 foreach my $hit (@$server_res) {
3185 my $hostname = @$hit[0];
3186 my $hostkey = &create_passwd;
3188 # add already connected clients to registration message
3189 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3190 &add_content2xml_hash($myhash, 'key', $hostkey);
3191 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3193 # add locally loaded gosa-si modules to registration message
3194 my $loaded_modules = {};
3195 while (my ($package, $pck_info) = each %$known_modules) {
3196 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3197 foreach my $act_module (keys(%{@$pck_info[2]})) {
3198 $loaded_modules->{$act_module} = "";
3199 }
3200 }
3202 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3204 # add macaddress to registration message
3205 my ($host_ip, $host_port) = split(/:/, $hostname);
3206 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3207 my $network_interface= &get_interface_for_ip($local_ip);
3208 my $host_mac = &get_mac_for_interface($network_interface);
3209 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3211 # build registration message and send it
3212 my $foreign_server_msg = &create_xml_string($myhash);
3213 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3214 }
3216 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3217 return;
3218 }
3221 #==== MAIN = main ==============================================================
3222 # parse commandline options
3223 Getopt::Long::Configure( "bundling" );
3224 GetOptions("h|help" => \&usage,
3225 "c|config=s" => \$cfg_file,
3226 "f|foreground" => \$foreground,
3227 "v|verbose+" => \$verbose,
3228 "no-arp+" => \$no_arp,
3229 );
3231 # read and set config parameters
3232 &check_cmdline_param ;
3233 &read_configfile($cfg_file, %cfg_defaults);
3234 &check_pid;
3236 $SIG{CHLD} = 'IGNORE';
3238 # forward error messages to logfile
3239 if( ! $foreground ) {
3240 open( STDIN, '+>/dev/null' );
3241 open( STDOUT, '+>&STDIN' );
3242 open( STDERR, '+>&STDIN' );
3243 }
3245 # Just fork, if we are not in foreground mode
3246 if( ! $foreground ) {
3247 chdir '/' or die "Can't chdir to /: $!";
3248 $pid = fork;
3249 setsid or die "Can't start a new session: $!";
3250 umask 0;
3251 } else {
3252 $pid = $$;
3253 }
3255 # Do something useful - put our PID into the pid_file
3256 if( 0 != $pid ) {
3257 open( LOCK_FILE, ">$pid_file" );
3258 print LOCK_FILE "$pid\n";
3259 close( LOCK_FILE );
3260 if( !$foreground ) {
3261 exit( 0 )
3262 };
3263 }
3265 # parse head url and revision from svn
3266 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3267 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3268 $server_headURL = defined $1 ? $1 : 'unknown' ;
3269 $server_revision = defined $2 ? $2 : 'unknown' ;
3270 if ($server_headURL =~ /\/tag\// ||
3271 $server_headURL =~ /\/branches\// ) {
3272 $server_status = "stable";
3273 } else {
3274 $server_status = "developmental" ;
3275 }
3277 # Prepare log file
3278 $root_uid = getpwnam('root');
3279 $adm_gid = getgrnam('adm');
3280 chmod(0640, $log_file);
3281 chown($root_uid, $adm_gid, $log_file);
3282 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3284 daemon_log(" ", 1);
3285 daemon_log("$0 started!", 1);
3286 daemon_log("status: $server_status", 1);
3287 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3289 {
3290 no strict "refs";
3292 if ($db_module eq "DBmysql") {
3293 # connect to incoming_db
3294 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3296 # connect to gosa-si job queue
3297 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3299 # connect to known_clients_db
3300 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3302 # connect to foreign_clients_db
3303 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3305 # connect to known_server_db
3306 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3308 # connect to login_usr_db
3309 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3311 # connect to fai_server_db
3312 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3314 # connect to fai_release_db
3315 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3317 # connect to packages_list_db
3318 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3320 # connect to messaging_db
3321 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3323 } elsif ($db_module eq "DBsqlite") {
3324 # connect to incoming_db
3325 unlink($incoming_file_name);
3326 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3328 # connect to gosa-si job queue
3329 unlink($job_queue_file_name); ## just for debugging
3330 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3331 chmod(0660, $job_queue_file_name);
3332 chown($root_uid, $adm_gid, $job_queue_file_name);
3334 # connect to known_clients_db
3335 unlink($known_clients_file_name); ## just for debugging
3336 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3337 chmod(0660, $known_clients_file_name);
3338 chown($root_uid, $adm_gid, $known_clients_file_name);
3340 # connect to foreign_clients_db
3341 unlink($foreign_clients_file_name);
3342 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3343 chmod(0660, $foreign_clients_file_name);
3344 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3346 # connect to known_server_db
3347 unlink($known_server_file_name);
3348 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3349 chmod(0660, $known_server_file_name);
3350 chown($root_uid, $adm_gid, $known_server_file_name);
3352 # connect to login_usr_db
3353 unlink($login_users_file_name);
3354 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3355 chmod(0660, $login_users_file_name);
3356 chown($root_uid, $adm_gid, $login_users_file_name);
3358 # connect to fai_server_db
3359 unlink($fai_server_file_name);
3360 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3361 chmod(0660, $fai_server_file_name);
3362 chown($root_uid, $adm_gid, $fai_server_file_name);
3364 # connect to fai_release_db
3365 unlink($fai_release_file_name);
3366 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3367 chmod(0660, $fai_release_file_name);
3368 chown($root_uid, $adm_gid, $fai_release_file_name);
3370 # connect to packages_list_db
3371 #unlink($packages_list_file_name);
3372 unlink($packages_list_under_construction);
3373 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3374 chmod(0660, $packages_list_file_name);
3375 chown($root_uid, $adm_gid, $packages_list_file_name);
3377 # connect to messaging_db
3378 unlink($messaging_file_name);
3379 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3380 chmod(0660, $messaging_file_name);
3381 chown($root_uid, $adm_gid, $messaging_file_name);
3382 }
3383 }
3385 # Creating tables
3386 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3387 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3388 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3389 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3390 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3391 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3392 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3393 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3394 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3395 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3398 # create xml object used for en/decrypting
3399 $xml = new XML::Simple();
3402 # foreign servers
3403 my @foreign_server_list;
3405 # add foreign server from cfg file
3406 if ($foreign_server_string ne "") {
3407 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3408 foreach my $foreign_server (@cfg_foreign_server_list) {
3409 push(@foreign_server_list, $foreign_server);
3410 }
3412 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3413 }
3415 # Perform a DNS lookup for server registration if flag is true
3416 if ($dns_lookup eq "true") {
3417 # Add foreign server from dns
3418 my @tmp_servers;
3419 if (not $server_domain) {
3420 # Try our DNS Searchlist
3421 for my $domain(get_dns_domains()) {
3422 chomp($domain);
3423 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3424 if(@$tmp_domains) {
3425 for my $tmp_server(@$tmp_domains) {
3426 push @tmp_servers, $tmp_server;
3427 }
3428 }
3429 }
3430 if(@tmp_servers && length(@tmp_servers)==0) {
3431 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3432 }
3433 } else {
3434 @tmp_servers = &get_server_addresses($server_domain);
3435 if( 0 == @tmp_servers ) {
3436 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3437 }
3438 }
3440 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3442 foreach my $server (@tmp_servers) {
3443 unshift(@foreign_server_list, $server);
3444 }
3445 } else {
3446 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3447 }
3450 # eliminate duplicate entries
3451 @foreign_server_list = &del_doubles(@foreign_server_list);
3452 my $all_foreign_server = join(", ", @foreign_server_list);
3453 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3455 # add all found foreign servers to known_server
3456 my $act_timestamp = &get_time();
3457 foreach my $foreign_server (@foreign_server_list) {
3459 # do not add myself to known_server_db
3460 if (&is_local($foreign_server)) { next; }
3461 ######################################
3463 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3464 primkey=>['hostname'],
3465 hostname=>$foreign_server,
3466 macaddress=>"",
3467 status=>'not_jet_registered',
3468 hostkey=>"none",
3469 loaded_modules => "none",
3470 timestamp=>$act_timestamp,
3471 } );
3472 }
3475 # Import all modules
3476 &import_modules;
3478 # Check wether all modules are gosa-si valid passwd check
3479 &password_check;
3481 # Prepare for using Opsi
3482 if ($opsi_enabled eq "true") {
3483 use JSON::RPC::Client;
3484 use XML::Quote qw(:all);
3485 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3486 $opsi_client = new JSON::RPC::Client;
3487 }
3490 POE::Component::Server::TCP->new(
3491 Alias => "TCP_SERVER",
3492 Port => $server_port,
3493 ClientInput => sub {
3494 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3495 my $session_id = $session->ID;
3496 my $remote_ip = $heap->{'remote_ip'};
3497 push(@msgs_to_decrypt, $input);
3498 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3499 $kernel->yield("msg_to_decrypt");
3500 },
3501 InlineStates => {
3502 msg_to_decrypt => \&msg_to_decrypt,
3503 next_task => \&next_task,
3504 task_result => \&handle_task_result,
3505 task_done => \&handle_task_done,
3506 task_debug => \&handle_task_debug,
3507 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3508 }
3509 );
3511 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3513 # create session for repeatedly checking the job queue for jobs
3514 POE::Session->create(
3515 inline_states => {
3516 _start => \&session_start,
3517 register_at_foreign_servers => \®ister_at_foreign_servers,
3518 sig_handler => \&sig_handler,
3519 next_task => \&next_task,
3520 task_result => \&handle_task_result,
3521 task_done => \&handle_task_done,
3522 task_debug => \&handle_task_debug,
3523 watch_for_next_tasks => \&watch_for_next_tasks,
3524 watch_for_new_messages => \&watch_for_new_messages,
3525 watch_for_delivery_messages => \&watch_for_delivery_messages,
3526 watch_for_done_messages => \&watch_for_done_messages,
3527 watch_for_new_jobs => \&watch_for_new_jobs,
3528 watch_for_modified_jobs => \&watch_for_modified_jobs,
3529 watch_for_done_jobs => \&watch_for_done_jobs,
3530 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3531 watch_for_old_known_clients => \&watch_for_old_known_clients,
3532 create_packages_list_db => \&run_create_packages_list_db,
3533 create_fai_server_db => \&run_create_fai_server_db,
3534 create_fai_release_db => \&run_create_fai_release_db,
3535 recreate_packages_db => \&run_recreate_packages_db,
3536 session_run_result => \&session_run_result,
3537 session_run_debug => \&session_run_debug,
3538 session_run_done => \&session_run_done,
3539 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3540 }
3541 );
3544 POE::Kernel->run();
3545 exit;