b19e8278f559e88dc94b0dc1a519ceb13c2b280e
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)");
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 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1009 my $jobdb_id = $1;
1011 $answer =~ /<header>(.*)<\/header>/;
1012 my $job_header = $1;
1014 $answer =~ /<target>(.*)<\/target>/;
1015 my $job_target = $1;
1017 # sending msg failed
1018 if( $error ) {
1020 # JUST FOR TESTING
1021 daemon_log("D \n$error\n$answer");
1023 # set jobs to done, jobs do not need to deliver their message in any case
1024 if (($job_header eq "trigger_action_localboot")
1025 ||($job_header eq "trigger_action_lock")
1026 ||($job_header eq "trigger_action_halt")
1027 ) {
1028 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1029 my $res = $job_db->update_dbentry($sql_statement);
1031 # reactivate jobs, jobs need to deliver their message
1032 } elsif (($job_header eq "trigger_action_activate")
1033 ||($job_header eq "trigger_action_update")
1034 ||($job_header eq "trigger_action_reinstall")
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 my $res = $job_db->update_dbentry($sql_statement);
1044 &daemon_log("D \n$sql_statement\n$answer", 1);
1046 }
1048 # sending msg was successful
1049 } else {
1050 # set jobs localboot, lock, activate, halt, reboot and wake to done
1051 # jobs reinstall, update, inst_update do themself setting to done
1052 if (($job_header eq "trigger_action_localboot")
1053 ||($job_header eq "trigger_action_lock")
1054 ||($job_header eq "trigger_action_activate")
1055 ||($job_header eq "trigger_action_halt")
1056 ||($job_header eq "trigger_action_reboot")
1057 ||($job_header eq "trigger_action_wake")
1058 ) {
1060 my $sql_statement = "UPDATE $job_queue_tn ".
1061 "SET status='done' ".
1062 "WHERE id=$jobdb_id AND status='processed'";
1063 my $res = $job_db->update_dbentry($sql_statement);
1064 }
1065 }
1066 }
1067 }
1069 sub reactivate_job_with_delay {
1070 my ($session_id, $target, $header, $delay) = @_ ;
1071 # 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
1073 if (not defined $delay) { $delay = 30 } ;
1074 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1076 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1077 my $res = $job_db->update_dbentry($sql);
1078 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1079 "cause client '$target' is currently not available", 5);
1080 daemon_log("$session_id $sql", 7);
1081 return;
1082 }
1085 sub sig_handler {
1086 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1087 daemon_log("0 INFO got signal '$signal'", 1);
1088 $kernel->sig_handled();
1089 return;
1090 }
1093 sub msg_to_decrypt {
1094 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1095 my $session_id = $session->ID;
1096 my ($msg, $msg_hash, $module);
1097 my $error = 0;
1099 # hole neue msg aus @msgs_to_decrypt
1100 my $next_msg = shift @msgs_to_decrypt;
1102 # msg is from a new client or gosa
1103 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1105 # msg is from a gosa-si-server
1106 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1107 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1108 }
1109 # msg is from a gosa-si-client
1110 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1111 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1112 }
1113 # an error occurred
1114 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1115 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1116 # could not understand a msg from its server the client cause a re-registering process
1117 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1118 "' to cause a re-registering of the client if necessary", 3);
1119 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1120 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1121 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1122 my $host_name = $hit->{'hostname'};
1123 my $host_key = $hit->{'hostkey'};
1124 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1125 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1126 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1127 }
1128 $error++;
1129 }
1132 my $header;
1133 my $target;
1134 my $source;
1135 my $done = 0;
1136 my $sql;
1137 my $res;
1139 # check whether this message should be processed here
1140 if ($error == 0) {
1141 $header = @{$msg_hash->{'header'}}[0];
1142 $target = @{$msg_hash->{'target'}}[0];
1143 $source = @{$msg_hash->{'source'}}[0];
1144 my $not_found_in_known_clients_db = 0;
1145 my $not_found_in_known_server_db = 0;
1146 my $not_found_in_foreign_clients_db = 0;
1147 my $local_address;
1148 my $local_mac;
1149 my ($target_ip, $target_port) = split(':', $target);
1151 # Determine the local ip address if target is an ip address
1152 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1153 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1154 } else {
1155 $local_address = $server_address;
1156 }
1158 # Determine the local mac address if target is a mac address
1159 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) {
1160 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1161 my $network_interface= &get_interface_for_ip($loc_ip);
1162 $local_mac = &get_mac_for_interface($network_interface);
1163 } else {
1164 $local_mac = $server_mac_address;
1165 }
1167 # target and source is equal to GOSA -> process here
1168 if (not $done) {
1169 if ($target eq "GOSA" && $source eq "GOSA") {
1170 $done = 1;
1171 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1172 }
1173 }
1175 # target is own address without forward_to_gosa-tag -> process here
1176 if (not $done) {
1177 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1178 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1179 $done = 1;
1180 if ($source eq "GOSA") {
1181 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1182 }
1183 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1184 }
1185 }
1187 # target is a client address in known_clients -> process here
1188 if (not $done) {
1189 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1190 $res = $known_clients_db->select_dbentry($sql);
1191 if (keys(%$res) > 0) {
1192 $done = 1;
1193 my $hostname = $res->{1}->{'hostname'};
1194 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1195 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1196 if ($source eq "GOSA") {
1197 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1198 }
1199 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1201 } else {
1202 $not_found_in_known_clients_db = 1;
1203 }
1204 }
1206 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1207 if (not $done) {
1208 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1209 my $gosa_at;
1210 my $gosa_session_id;
1211 if (($target eq $local_address) && (defined $forward_to_gosa)){
1212 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1213 if ($gosa_at ne $local_address) {
1214 $done = 1;
1215 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1216 }
1217 }
1218 }
1220 # if message should be processed here -> add message to incoming_db
1221 if ($done) {
1222 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1223 # so gosa-si-server knows how to process this kind of messages
1224 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1225 $module = "GosaPackages";
1226 }
1228 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1229 primkey=>[],
1230 headertag=>$header,
1231 targettag=>$target,
1232 xmlmessage=>&encode_base64($msg),
1233 timestamp=>&get_time,
1234 module=>$module,
1235 sessionid=>$session_id,
1236 } );
1238 }
1240 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1241 if (not $done) {
1242 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1243 my $gosa_at;
1244 my $gosa_session_id;
1245 if (($target eq $local_address) && (defined $forward_to_gosa)){
1246 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1247 if ($gosa_at eq $local_address) {
1248 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1249 if( defined $session_reference ) {
1250 $heap = $session_reference->get_heap();
1251 }
1252 if(exists $heap->{'client'}) {
1253 $msg = &encrypt_msg($msg, $GosaPackages_key);
1254 $heap->{'client'}->put($msg);
1255 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1256 }
1257 $done = 1;
1258 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1259 }
1260 }
1262 }
1264 # target is a client address in foreign_clients -> forward to registration server
1265 if (not $done) {
1266 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1267 $res = $foreign_clients_db->select_dbentry($sql);
1268 if (keys(%$res) > 0) {
1269 my $hostname = $res->{1}->{'hostname'};
1270 my ($host_ip, $host_port) = split(/:/, $hostname);
1271 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1272 my $regserver = $res->{1}->{'regserver'};
1273 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1274 my $res = $known_server_db->select_dbentry($sql);
1275 if (keys(%$res) > 0) {
1276 my $regserver_key = $res->{1}->{'hostkey'};
1277 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1278 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1279 if ($source eq "GOSA") {
1280 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1281 }
1282 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1283 }
1284 $done = 1;
1285 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1286 } else {
1287 $not_found_in_foreign_clients_db = 1;
1288 }
1289 }
1291 # target is a server address -> forward to server
1292 if (not $done) {
1293 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1294 $res = $known_server_db->select_dbentry($sql);
1295 if (keys(%$res) > 0) {
1296 my $hostkey = $res->{1}->{'hostkey'};
1298 if ($source eq "GOSA") {
1299 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1300 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1302 }
1304 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1305 $done = 1;
1306 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1307 } else {
1308 $not_found_in_known_server_db = 1;
1309 }
1310 }
1313 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1314 if ( $not_found_in_foreign_clients_db
1315 && $not_found_in_known_server_db
1316 && $not_found_in_known_clients_db) {
1317 &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);
1318 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1319 $module = "GosaPackages";
1320 }
1321 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1322 primkey=>[],
1323 headertag=>$header,
1324 targettag=>$target,
1325 xmlmessage=>&encode_base64($msg),
1326 timestamp=>&get_time,
1327 module=>$module,
1328 sessionid=>$session_id,
1329 } );
1330 $done = 1;
1331 }
1334 if (not $done) {
1335 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1336 if ($source eq "GOSA") {
1337 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1338 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1340 my $session_reference = $kernel->ID_id_to_session($session_id);
1341 if( defined $session_reference ) {
1342 $heap = $session_reference->get_heap();
1343 }
1344 if(exists $heap->{'client'}) {
1345 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1346 $heap->{'client'}->put($error_msg);
1347 }
1348 }
1349 }
1351 }
1353 return;
1354 }
1357 sub next_task {
1358 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1359 my $running_task = POE::Wheel::Run->new(
1360 Program => sub { process_task($session, $heap, $task) },
1361 StdioFilter => POE::Filter::Reference->new(),
1362 StdoutEvent => "task_result",
1363 StderrEvent => "task_debug",
1364 CloseEvent => "task_done",
1365 );
1366 $heap->{task}->{ $running_task->ID } = $running_task;
1367 }
1369 sub handle_task_result {
1370 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1371 my $client_answer = $result->{'answer'};
1372 if( $client_answer =~ s/session_id=(\d+)$// ) {
1373 my $session_id = $1;
1374 if( defined $session_id ) {
1375 my $session_reference = $kernel->ID_id_to_session($session_id);
1376 if( defined $session_reference ) {
1377 $heap = $session_reference->get_heap();
1378 }
1379 }
1381 if(exists $heap->{'client'}) {
1382 $heap->{'client'}->put($client_answer);
1383 }
1384 }
1385 $kernel->sig(CHLD => "child_reap");
1386 }
1388 sub handle_task_debug {
1389 my $result = $_[ARG0];
1390 print STDERR "$result\n";
1391 }
1393 sub handle_task_done {
1394 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1395 delete $heap->{task}->{$task_id};
1396 }
1398 sub process_task {
1399 no strict "refs";
1400 #CHECK: Not @_[...]?
1401 my ($session, $heap, $task) = @_;
1402 my $error = 0;
1403 my $answer_l;
1404 my ($answer_header, @answer_target_l, $answer_source);
1405 my $client_answer = "";
1407 # prepare all variables needed to process message
1408 #my $msg = $task->{'xmlmessage'};
1409 my $msg = &decode_base64($task->{'xmlmessage'});
1410 my $incoming_id = $task->{'id'};
1411 my $module = $task->{'module'};
1412 my $header = $task->{'headertag'};
1413 my $session_id = $task->{'sessionid'};
1414 my $msg_hash;
1415 eval {
1416 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1417 };
1418 daemon_log("ERROR: XML failure '$@'") if ($@);
1419 my $source = @{$msg_hash->{'source'}}[0];
1421 # set timestamp of incoming client uptodate, so client will not
1422 # be deleted from known_clients because of expiration
1423 my $act_time = &get_time();
1424 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1425 my $res = $known_clients_db->exec_statement($sql);
1427 ######################
1428 # process incoming msg
1429 if( $error == 0) {
1430 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1431 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1432 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1434 if ( 0 < @{$answer_l} ) {
1435 my $answer_str = join("\n", @{$answer_l});
1436 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1437 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1438 }
1439 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1440 } else {
1441 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1442 }
1444 }
1445 if( !$answer_l ) { $error++ };
1447 ########
1448 # answer
1449 if( $error == 0 ) {
1451 foreach my $answer ( @{$answer_l} ) {
1452 # check outgoing msg to xml validity
1453 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1454 if( not defined $answer_hash ) { next; }
1456 $answer_header = @{$answer_hash->{'header'}}[0];
1457 @answer_target_l = @{$answer_hash->{'target'}};
1458 $answer_source = @{$answer_hash->{'source'}}[0];
1460 # deliver msg to all targets
1461 foreach my $answer_target ( @answer_target_l ) {
1463 # targets of msg are all gosa-si-clients in known_clients_db
1464 if( $answer_target eq "*" ) {
1465 # answer is for all clients
1466 my $sql_statement= "SELECT * FROM known_clients";
1467 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1468 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1469 my $host_name = $hit->{hostname};
1470 my $host_key = $hit->{hostkey};
1471 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1472 &update_jobdb_status_for_send_msgs($answer, $error);
1473 }
1474 }
1476 # targets of msg are all gosa-si-server in known_server_db
1477 elsif( $answer_target eq "KNOWN_SERVER" ) {
1478 # answer is for all server in known_server
1479 my $sql_statement= "SELECT * FROM $known_server_tn";
1480 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1481 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1482 my $host_name = $hit->{hostname};
1483 my $host_key = $hit->{hostkey};
1484 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1485 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1486 &update_jobdb_status_for_send_msgs($answer, $error);
1487 }
1488 }
1490 # target of msg is GOsa
1491 elsif( $answer_target eq "GOSA" ) {
1492 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1493 my $add_on = "";
1494 if( defined $session_id ) {
1495 $add_on = ".session_id=$session_id";
1496 }
1497 # answer is for GOSA and has to returned to connected client
1498 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1499 $client_answer = $gosa_answer.$add_on;
1500 }
1502 # target of msg is job queue at this host
1503 elsif( $answer_target eq "JOBDB") {
1504 $answer =~ /<header>(\S+)<\/header>/;
1505 my $header;
1506 if( defined $1 ) { $header = $1; }
1507 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1508 &update_jobdb_status_for_send_msgs($answer, $error);
1509 }
1511 # Target of msg is a mac address
1512 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 ) {
1513 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1515 # Looking for macaddress in known_clients
1516 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1517 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1518 my $found_ip_flag = 0;
1519 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1520 my $host_name = $hit->{hostname};
1521 my $host_key = $hit->{hostkey};
1522 $answer =~ s/$answer_target/$host_name/g;
1523 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1524 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1525 &update_jobdb_status_for_send_msgs($answer, $error);
1526 $found_ip_flag++ ;
1527 }
1529 # Looking for macaddress in foreign_clients
1530 if ($found_ip_flag == 0) {
1531 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1532 my $res = $foreign_clients_db->select_dbentry($sql);
1533 while( my ($hit_num, $hit) = each %{ $res } ) {
1534 my $host_name = $hit->{hostname};
1535 my $reg_server = $hit->{regserver};
1536 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1538 # Fetch key for reg_server
1539 my $reg_server_key;
1540 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1541 my $res = $known_server_db->select_dbentry($sql);
1542 if (exists $res->{1}) {
1543 $reg_server_key = $res->{1}->{'hostkey'};
1544 } else {
1545 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1546 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1547 $reg_server_key = undef;
1548 }
1550 # Send answer to server where client is registered
1551 if (defined $reg_server_key) {
1552 $answer =~ s/$answer_target/$host_name/g;
1553 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1554 &update_jobdb_status_for_send_msgs($answer, $error);
1555 $found_ip_flag++ ;
1556 }
1557 }
1558 }
1560 # No mac to ip matching found
1561 if( $found_ip_flag == 0) {
1562 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1563 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1564 }
1566 # Answer is for one specific host
1567 } else {
1568 # get encrypt_key
1569 my $encrypt_key = &get_encrypt_key($answer_target);
1570 if( not defined $encrypt_key ) {
1571 # unknown target
1572 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1573 next;
1574 }
1575 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1576 &update_jobdb_status_for_send_msgs($answer, $error);
1577 }
1578 }
1579 }
1580 }
1582 my $filter = POE::Filter::Reference->new();
1583 my %result = (
1584 status => "seems ok to me",
1585 answer => $client_answer,
1586 );
1588 my $output = $filter->put( [ \%result ] );
1589 print @$output;
1592 }
1594 sub session_start {
1595 my ($kernel) = $_[KERNEL];
1596 $global_kernel = $kernel;
1597 $kernel->yield('register_at_foreign_servers');
1598 $kernel->yield('create_fai_server_db', $fai_server_tn );
1599 $kernel->yield('create_fai_release_db', $fai_release_tn );
1600 $kernel->yield('watch_for_next_tasks');
1601 $kernel->sig(USR1 => "sig_handler");
1602 $kernel->sig(USR2 => "recreate_packages_db");
1603 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1604 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1605 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1606 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1607 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1608 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1609 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1611 # Start opsi check
1612 if ($opsi_enabled eq "true") {
1613 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1614 }
1616 }
1619 sub watch_for_done_jobs {
1620 #CHECK: $heap for what?
1621 my ($kernel,$heap) = @_[KERNEL, HEAP];
1623 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1624 my $res = $job_db->select_dbentry( $sql_statement );
1626 while( my ($id, $hit) = each %{$res} ) {
1627 my $jobdb_id = $hit->{id};
1628 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1629 my $res = $job_db->del_dbentry($sql_statement);
1630 }
1632 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1633 }
1636 sub watch_for_opsi_jobs {
1637 my ($kernel) = $_[KERNEL];
1639 # 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
1640 # opsi install job is to parse the xml message. There is still the correct header.
1641 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1642 my $res = $job_db->select_dbentry( $sql_statement );
1644 # Ask OPSI for an update of the running jobs
1645 while (my ($id, $hit) = each %$res ) {
1646 # Determine current parameters of the job
1647 my $hostId = $hit->{'plainname'};
1648 my $macaddress = $hit->{'macaddress'};
1649 my $progress = $hit->{'progress'};
1651 my $result= {};
1653 # For hosts, only return the products that are or get installed
1654 my $callobj;
1655 $callobj = {
1656 method => 'getProductStates_hash',
1657 params => [ $hostId ],
1658 id => 1,
1659 };
1661 my $hres = $opsi_client->call($opsi_url, $callobj);
1662 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1663 if (not &check_opsi_res($hres)) {
1664 my $htmp= $hres->result->{$hostId};
1666 # Check state != not_installed or action == setup -> load and add
1667 my $products= 0;
1668 my $installed= 0;
1669 my $installing = 0;
1670 my $error= 0;
1671 my @installed_list;
1672 my @error_list;
1673 my $act_status = "none";
1674 foreach my $product (@{$htmp}){
1676 if ($product->{'installationStatus'} ne "not_installed" or
1677 $product->{'actionRequest'} eq "setup"){
1679 # Increase number of products for this host
1680 $products++;
1682 if ($product->{'installationStatus'} eq "failed"){
1683 $result->{$product->{'productId'}}= "error";
1684 unshift(@error_list, $product->{'productId'});
1685 $error++;
1686 }
1687 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1688 $result->{$product->{'productId'}}= "installed";
1689 unshift(@installed_list, $product->{'productId'});
1690 $installed++;
1691 }
1692 if ($product->{'installationStatus'} eq "installing"){
1693 $result->{$product->{'productId'}}= "installing";
1694 $installing++;
1695 $act_status = "installing - ".$product->{'productId'};
1696 }
1697 }
1698 }
1700 # Estimate "rough" progress, avoid division by zero
1701 if ($products == 0) {
1702 $result->{'progress'}= 0;
1703 } else {
1704 $result->{'progress'}= int($installed * 100 / $products);
1705 }
1707 # Set updates in job queue
1708 if ((not $error) && (not $installing) && ($installed)) {
1709 $act_status = "installed - ".join(", ", @installed_list);
1710 }
1711 if ($error) {
1712 $act_status = "error - ".join(", ", @error_list);
1713 }
1714 if ($progress ne $result->{'progress'} ) {
1715 # Updating progress and result
1716 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1717 my $update_res = $job_db->update_dbentry($update_statement);
1718 }
1719 if ($progress eq 100) {
1720 # Updateing status
1721 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1722 if ($error) {
1723 $done_statement .= "status='error'";
1724 } else {
1725 $done_statement .= "status='done'";
1726 }
1727 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1728 my $done_res = $job_db->update_dbentry($done_statement);
1729 }
1732 }
1733 }
1735 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1736 }
1739 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1740 sub watch_for_modified_jobs {
1741 my ($kernel,$heap) = @_[KERNEL, HEAP];
1743 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1744 my $res = $job_db->select_dbentry( $sql_statement );
1746 # if db contains no jobs which should be update, do nothing
1747 if (keys %$res != 0) {
1749 if ($job_synchronization eq "true") {
1750 # make out of the db result a gosa-si message
1751 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1753 # update all other SI-server
1754 &inform_all_other_si_server($update_msg);
1755 }
1757 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1758 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1759 $res = $job_db->update_dbentry($sql_statement);
1760 }
1762 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1763 }
1766 sub watch_for_new_jobs {
1767 if($watch_for_new_jobs_in_progress == 0) {
1768 $watch_for_new_jobs_in_progress = 1;
1769 my ($kernel,$heap) = @_[KERNEL, HEAP];
1771 # check gosa job quaeue for jobs with executable timestamp
1772 my $timestamp = &get_time();
1773 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1774 my $res = $job_db->exec_statement( $sql_statement );
1776 # Merge all new jobs that would do the same actions
1777 my @drops;
1778 my $hits;
1779 foreach my $hit (reverse @{$res} ) {
1780 my $macaddress= lc @{$hit}[8];
1781 my $headertag= @{$hit}[5];
1782 if(
1783 defined($hits->{$macaddress}) &&
1784 defined($hits->{$macaddress}->{$headertag}) &&
1785 defined($hits->{$macaddress}->{$headertag}[0])
1786 ) {
1787 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1788 }
1789 $hits->{$macaddress}->{$headertag}= $hit;
1790 }
1792 # Delete new jobs with a matching job in state 'processing'
1793 foreach my $macaddress (keys %{$hits}) {
1794 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1795 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1796 if(defined($jobdb_id)) {
1797 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1798 my $res = $job_db->exec_statement( $sql_statement );
1799 foreach my $hit (@{$res}) {
1800 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1801 }
1802 } else {
1803 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1804 }
1805 }
1806 }
1808 # Commit deletion
1809 $job_db->exec_statementlist(\@drops);
1811 # Look for new jobs that could be executed
1812 foreach my $macaddress (keys %{$hits}) {
1814 # Look if there is an executing job
1815 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1816 my $res = $job_db->exec_statement( $sql_statement );
1818 # Skip new jobs for host if there is a processing job
1819 if(defined($res) and defined @{$res}[0]) {
1820 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1821 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1822 if(@{$row}[5] eq 'trigger_action_reinstall') {
1823 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1824 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1825 if(defined($res_2) and defined @{$res_2}[0]) {
1826 # Set status from goto-activation to 'waiting' and update timestamp
1827 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1828 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1829 }
1830 }
1831 next;
1832 }
1834 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1835 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1836 if(defined($jobdb_id)) {
1837 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1839 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1840 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1841 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1843 # expect macaddress is unique!!!!!!
1844 my $target = $res_hash->{1}->{hostname};
1846 # change header
1847 $job_msg =~ s/<header>job_/<header>gosa_/;
1849 # add sqlite_id
1850 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1852 $job_msg =~ /<header>(\S+)<\/header>/;
1853 my $header = $1 ;
1854 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1856 # update status in job queue to ...
1857 # ... 'processing', for jobs: 'reinstall', 'update'
1858 if (($header =~ /gosa_trigger_action_reinstall/) || ($header =~ /gosa_trigger_action_update/)) {
1859 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1860 my $dbres = $job_db->update_dbentry($sql_statement);
1861 }
1863 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1864 else {
1865 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1866 my $dbres = $job_db->update_dbentry($sql_statement);
1867 }
1870 # We don't want parallel processing
1871 last;
1872 }
1873 }
1874 }
1876 $watch_for_new_jobs_in_progress = 0;
1877 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1878 }
1879 }
1882 sub watch_for_new_messages {
1883 my ($kernel,$heap) = @_[KERNEL, HEAP];
1884 my @coll_user_msg; # collection list of outgoing messages
1886 # check messaging_db for new incoming messages with executable timestamp
1887 my $timestamp = &get_time();
1888 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1889 my $res = $messaging_db->exec_statement( $sql_statement );
1890 foreach my $hit (@{$res}) {
1892 # create outgoing messages
1893 my $message_to = @{$hit}[3];
1894 # translate message_to to plain login name
1895 my @message_to_l = split(/,/, $message_to);
1896 my %receiver_h;
1897 foreach my $receiver (@message_to_l) {
1898 if ($receiver =~ /^u_([\s\S]*)$/) {
1899 $receiver_h{$receiver} = 0;
1900 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1901 my $group_name = $1;
1902 # fetch all group members from ldap and add them to receiver hash
1903 my $ldap_handle = &get_ldap_handle();
1904 if (defined $ldap_handle) {
1905 my $mesg = $ldap_handle->search(
1906 base => $ldap_base,
1907 scope => 'sub',
1908 attrs => ['memberUid'],
1909 filter => "cn=$group_name",
1910 );
1911 if ($mesg->count) {
1912 my @entries = $mesg->entries;
1913 foreach my $entry (@entries) {
1914 my @receivers= $entry->get_value("memberUid");
1915 foreach my $receiver (@receivers) {
1916 $receiver_h{$receiver} = 0;
1917 }
1918 }
1919 }
1920 # translating errors ?
1921 if ($mesg->code) {
1922 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1923 }
1924 # ldap handle error ?
1925 } else {
1926 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1927 }
1928 } else {
1929 my $sbjct = &encode_base64(@{$hit}[1]);
1930 my $msg = &encode_base64(@{$hit}[7]);
1931 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1932 }
1933 }
1934 my @receiver_l = keys(%receiver_h);
1936 my $message_id = @{$hit}[0];
1938 #add each outgoing msg to messaging_db
1939 my $receiver;
1940 foreach $receiver (@receiver_l) {
1941 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1942 "VALUES ('".
1943 $message_id."', '". # id
1944 @{$hit}[1]."', '". # subject
1945 @{$hit}[2]."', '". # message_from
1946 $receiver."', '". # message_to
1947 "none"."', '". # flag
1948 "out"."', '". # direction
1949 @{$hit}[6]."', '". # delivery_time
1950 @{$hit}[7]."', '". # message
1951 $timestamp."'". # timestamp
1952 ")";
1953 &daemon_log("M DEBUG: $sql_statement", 1);
1954 my $res = $messaging_db->exec_statement($sql_statement);
1955 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1956 }
1958 # set incoming message to flag d=deliverd
1959 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1960 &daemon_log("M DEBUG: $sql_statement", 7);
1961 $res = $messaging_db->update_dbentry($sql_statement);
1962 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1963 }
1965 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1966 return;
1967 }
1969 sub watch_for_delivery_messages {
1970 my ($kernel, $heap) = @_[KERNEL, HEAP];
1972 # select outgoing messages
1973 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1974 #&daemon_log("0 DEBUG: $sql", 7);
1975 my $res = $messaging_db->exec_statement( $sql_statement );
1977 # build out msg for each usr
1978 foreach my $hit (@{$res}) {
1979 my $receiver = @{$hit}[3];
1980 my $msg_id = @{$hit}[0];
1981 my $subject = @{$hit}[1];
1982 my $message = @{$hit}[7];
1984 # resolve usr -> host where usr is logged in
1985 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1986 #&daemon_log("0 DEBUG: $sql", 7);
1987 my $res = $login_users_db->exec_statement($sql);
1989 # receiver is logged in nowhere
1990 if (not ref(@$res[0]) eq "ARRAY") { next; }
1992 # receiver ist logged in at a client registered at local server
1993 my $send_succeed = 0;
1994 foreach my $hit (@$res) {
1995 my $receiver_host = @$hit[0];
1996 my $delivered2host = 0;
1997 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1999 # Looking for host in know_clients_db
2000 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2001 my $res = $known_clients_db->exec_statement($sql);
2003 # Host is known in known_clients_db
2004 if (ref(@$res[0]) eq "ARRAY") {
2005 my $receiver_key = @{@{$res}[0]}[2];
2006 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2007 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2008 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2009 if ($error == 0 ) {
2010 $send_succeed++ ;
2011 $delivered2host++ ;
2012 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2013 } else {
2014 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2015 }
2016 }
2018 # Message already send, do not need to do anything more, otherwise ...
2019 if ($delivered2host) { next;}
2021 # ...looking for host in foreign_clients_db
2022 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2023 $res = $foreign_clients_db->exec_statement($sql);
2025 # Host is known in foreign_clients_db
2026 if (ref(@$res[0]) eq "ARRAY") {
2027 my $registration_server = @{@{$res}[0]}[2];
2029 # Fetch encryption key for registration server
2030 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2031 my $res = $known_server_db->exec_statement($sql);
2032 if (ref(@$res[0]) eq "ARRAY") {
2033 my $registration_server_key = @{@{$res}[0]}[3];
2034 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2035 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2036 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2037 if ($error == 0 ) {
2038 $send_succeed++ ;
2039 $delivered2host++ ;
2040 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2041 } else {
2042 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2043 }
2045 } else {
2046 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2047 "registrated at server '$registration_server', ".
2048 "but no data available in known_server_db ", 1);
2049 }
2050 }
2052 if (not $delivered2host) {
2053 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2054 }
2055 }
2057 if ($send_succeed) {
2058 # set outgoing msg at db to deliverd
2059 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2060 my $res = $messaging_db->exec_statement($sql);
2061 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2062 } else {
2063 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2064 }
2065 }
2067 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2068 return;
2069 }
2072 sub watch_for_done_messages {
2073 my ($kernel,$heap) = @_[KERNEL, HEAP];
2075 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2076 #&daemon_log("0 DEBUG: $sql", 7);
2077 my $res = $messaging_db->exec_statement($sql);
2079 foreach my $hit (@{$res}) {
2080 my $msg_id = @{$hit}[0];
2082 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2083 #&daemon_log("0 DEBUG: $sql", 7);
2084 my $res = $messaging_db->exec_statement($sql);
2086 # not all usr msgs have been seen till now
2087 if ( ref(@$res[0]) eq "ARRAY") { next; }
2089 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2090 #&daemon_log("0 DEBUG: $sql", 7);
2091 $res = $messaging_db->exec_statement($sql);
2093 }
2095 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2096 return;
2097 }
2100 sub watch_for_old_known_clients {
2101 my ($kernel,$heap) = @_[KERNEL, HEAP];
2103 my $sql_statement = "SELECT * FROM $known_clients_tn";
2104 my $res = $known_clients_db->select_dbentry( $sql_statement );
2106 my $act_time = int(&get_time());
2108 while ( my ($hit_num, $hit) = each %$res) {
2109 my $expired_timestamp = int($hit->{'timestamp'});
2110 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2111 my $dt = DateTime->new( year => $1,
2112 month => $2,
2113 day => $3,
2114 hour => $4,
2115 minute => $5,
2116 second => $6,
2117 );
2119 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2120 $expired_timestamp = $dt->ymd('').$dt->hms('');
2121 if ($act_time > $expired_timestamp) {
2122 my $hostname = $hit->{'hostname'};
2123 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2124 my $del_res = $known_clients_db->exec_statement($del_sql);
2126 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2127 }
2129 }
2131 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2132 }
2135 sub watch_for_next_tasks {
2136 my ($kernel,$heap) = @_[KERNEL, HEAP];
2138 my $sql = "SELECT * FROM $incoming_tn";
2139 my $res = $incoming_db->select_dbentry($sql);
2141 while ( my ($hit_num, $hit) = each %$res) {
2142 my $headertag = $hit->{'headertag'};
2143 if ($headertag =~ /^answer_(\d+)/) {
2144 # do not start processing, this message is for a still running POE::Wheel
2145 next;
2146 }
2147 my $message_id = $hit->{'id'};
2148 my $session_id = $hit->{'sessionid'};
2149 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2150 $kernel->yield('next_task', $hit);
2152 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2153 my $res = $incoming_db->exec_statement($sql);
2154 }
2156 $kernel->delay_set('watch_for_next_tasks', 1);
2157 }
2160 sub get_ldap_handle {
2161 my ($session_id) = @_;
2162 my $heap;
2163 my $ldap_handle;
2165 if (not defined $session_id ) { $session_id = 0 };
2166 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2168 if ($session_id == 0) {
2169 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2170 $ldap_handle = Net::LDAP->new( $ldap_uri );
2171 if (defined $ldap_handle) {
2172 $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!");
2173 } else {
2174 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2175 }
2177 } else {
2178 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2179 if( defined $session_reference ) {
2180 $heap = $session_reference->get_heap();
2181 }
2183 if (not defined $heap) {
2184 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2185 return;
2186 }
2188 # TODO: This "if" is nonsense, because it doesn't prove that the
2189 # used handle is still valid - or if we've to reconnect...
2190 #if (not exists $heap->{ldap_handle}) {
2191 $ldap_handle = Net::LDAP->new( $ldap_uri );
2192 $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!");
2193 $heap->{ldap_handle} = $ldap_handle;
2194 #}
2195 }
2196 return $ldap_handle;
2197 }
2200 sub change_fai_state {
2201 my ($st, $targets, $session_id) = @_;
2202 $session_id = 0 if not defined $session_id;
2203 # Set FAI state to localboot
2204 my %mapActions= (
2205 reboot => '',
2206 update => 'softupdate',
2207 localboot => 'localboot',
2208 reinstall => 'install',
2209 rescan => '',
2210 wake => '',
2211 memcheck => 'memcheck',
2212 sysinfo => 'sysinfo',
2213 install => 'install',
2214 );
2216 # Return if this is unknown
2217 if (!exists $mapActions{ $st }){
2218 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2219 return;
2220 }
2222 my $state= $mapActions{ $st };
2224 my $ldap_handle = &get_ldap_handle($session_id);
2225 if( defined($ldap_handle) ) {
2227 # Build search filter for hosts
2228 my $search= "(&(objectClass=GOhard)";
2229 foreach (@{$targets}){
2230 $search.= "(macAddress=$_)";
2231 }
2232 $search.= ")";
2234 # If there's any host inside of the search string, procress them
2235 if (!($search =~ /macAddress/)){
2236 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2237 return;
2238 }
2240 # Perform search for Unit Tag
2241 my $mesg = $ldap_handle->search(
2242 base => $ldap_base,
2243 scope => 'sub',
2244 attrs => ['dn', 'FAIstate', 'objectClass'],
2245 filter => "$search"
2246 );
2248 if ($mesg->count) {
2249 my @entries = $mesg->entries;
2250 if (0 == @entries) {
2251 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2252 }
2254 foreach my $entry (@entries) {
2255 # Only modify entry if it is not set to '$state'
2256 if ($entry->get_value("FAIstate") ne "$state"){
2257 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2258 my $result;
2259 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2260 if (exists $tmp{'FAIobject'}){
2261 if ($state eq ''){
2262 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2263 } else {
2264 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2265 }
2266 } elsif ($state ne ''){
2267 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2268 }
2270 # Errors?
2271 if ($result->code){
2272 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2273 }
2274 } else {
2275 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2276 }
2277 }
2278 } else {
2279 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2280 }
2282 # if no ldap handle defined
2283 } else {
2284 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2285 }
2287 return;
2288 }
2291 sub change_goto_state {
2292 my ($st, $targets, $session_id) = @_;
2293 $session_id = 0 if not defined $session_id;
2295 # Switch on or off?
2296 my $state= $st eq 'active' ? 'active': 'locked';
2298 my $ldap_handle = &get_ldap_handle($session_id);
2299 if( defined($ldap_handle) ) {
2301 # Build search filter for hosts
2302 my $search= "(&(objectClass=GOhard)";
2303 foreach (@{$targets}){
2304 $search.= "(macAddress=$_)";
2305 }
2306 $search.= ")";
2308 # If there's any host inside of the search string, procress them
2309 if (!($search =~ /macAddress/)){
2310 return;
2311 }
2313 # Perform search for Unit Tag
2314 my $mesg = $ldap_handle->search(
2315 base => $ldap_base,
2316 scope => 'sub',
2317 attrs => ['dn', 'gotoMode'],
2318 filter => "$search"
2319 );
2321 if ($mesg->count) {
2322 my @entries = $mesg->entries;
2323 foreach my $entry (@entries) {
2325 # Only modify entry if it is not set to '$state'
2326 if ($entry->get_value("gotoMode") ne $state){
2328 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2329 my $result;
2330 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2332 # Errors?
2333 if ($result->code){
2334 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2335 }
2337 }
2338 }
2339 } else {
2340 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2341 }
2343 }
2344 }
2347 sub run_recreate_packages_db {
2348 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2349 my $session_id = $session->ID;
2350 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2351 $kernel->yield('create_fai_release_db', $fai_release_tn);
2352 $kernel->yield('create_fai_server_db', $fai_server_tn);
2353 return;
2354 }
2357 sub run_create_fai_server_db {
2358 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2359 my $session_id = $session->ID;
2360 my $task = POE::Wheel::Run->new(
2361 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2362 StdoutEvent => "session_run_result",
2363 StderrEvent => "session_run_debug",
2364 CloseEvent => "session_run_done",
2365 );
2367 $heap->{task}->{ $task->ID } = $task;
2368 return;
2369 }
2372 sub create_fai_server_db {
2373 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2374 my $result;
2376 if (not defined $session_id) { $session_id = 0; }
2377 my $ldap_handle = &get_ldap_handle();
2378 if(defined($ldap_handle)) {
2379 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2380 my $mesg= $ldap_handle->search(
2381 base => $ldap_base,
2382 scope => 'sub',
2383 attrs => ['FAIrepository', 'gosaUnitTag'],
2384 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2385 );
2386 if($mesg->{'resultCode'} == 0 &&
2387 $mesg->count != 0) {
2388 foreach my $entry (@{$mesg->{entries}}) {
2389 if($entry->exists('FAIrepository')) {
2390 # Add an entry for each Repository configured for server
2391 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2392 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2393 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2394 $result= $fai_server_db->add_dbentry( {
2395 table => $table_name,
2396 primkey => ['server', 'fai_release', 'tag'],
2397 server => $tmp_url,
2398 fai_release => $tmp_release,
2399 sections => $tmp_sections,
2400 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2401 } );
2402 }
2403 }
2404 }
2405 }
2406 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2408 # TODO: Find a way to post the 'create_packages_list_db' event
2409 if(not defined($dont_create_packages_list)) {
2410 &create_packages_list_db(undef, undef, $session_id);
2411 }
2412 }
2414 $ldap_handle->disconnect;
2415 return $result;
2416 }
2419 sub run_create_fai_release_db {
2420 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2421 my $session_id = $session->ID;
2422 my $task = POE::Wheel::Run->new(
2423 Program => sub { &create_fai_release_db($table_name, $session_id) },
2424 StdoutEvent => "session_run_result",
2425 StderrEvent => "session_run_debug",
2426 CloseEvent => "session_run_done",
2427 );
2429 $heap->{task}->{ $task->ID } = $task;
2430 return;
2431 }
2434 sub create_fai_release_db {
2435 my ($table_name, $session_id) = @_;
2436 my $result;
2438 # used for logging
2439 if (not defined $session_id) { $session_id = 0; }
2441 my $ldap_handle = &get_ldap_handle();
2442 if(defined($ldap_handle)) {
2443 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2444 my $mesg= $ldap_handle->search(
2445 base => $ldap_base,
2446 scope => 'sub',
2447 attrs => [],
2448 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2449 );
2450 if($mesg->{'resultCode'} == 0 &&
2451 $mesg->count != 0) {
2452 # Walk through all possible FAI container ou's
2453 my @sql_list;
2454 my $timestamp= &get_time();
2455 foreach my $ou (@{$mesg->{entries}}) {
2456 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2457 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2458 my @tmp_array=get_fai_release_entries($tmp_classes);
2459 if(@tmp_array) {
2460 foreach my $entry (@tmp_array) {
2461 if(defined($entry) && ref($entry) eq 'HASH') {
2462 my $sql=
2463 "INSERT INTO $table_name "
2464 ."(timestamp, fai_release, class, type, state) VALUES ("
2465 .$timestamp.","
2466 ."'".$entry->{'release'}."',"
2467 ."'".$entry->{'class'}."',"
2468 ."'".$entry->{'type'}."',"
2469 ."'".$entry->{'state'}."')";
2470 push @sql_list, $sql;
2471 }
2472 }
2473 }
2474 }
2475 }
2477 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2478 if(@sql_list) {
2479 unshift @sql_list, "DELETE FROM $table_name";
2480 $fai_release_db->exec_statementlist(\@sql_list);
2481 }
2482 daemon_log("$session_id DEBUG: Done with inserting",7);
2483 }
2484 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2485 }
2486 $ldap_handle->disconnect;
2487 return $result;
2488 }
2490 sub get_fai_types {
2491 my $tmp_classes = shift || return undef;
2492 my @result;
2494 foreach my $type(keys %{$tmp_classes}) {
2495 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2496 my $entry = {
2497 type => $type,
2498 state => $tmp_classes->{$type}[0],
2499 };
2500 push @result, $entry;
2501 }
2502 }
2504 return @result;
2505 }
2507 sub get_fai_state {
2508 my $result = "";
2509 my $tmp_classes = shift || return $result;
2511 foreach my $type(keys %{$tmp_classes}) {
2512 if(defined($tmp_classes->{$type}[0])) {
2513 $result = $tmp_classes->{$type}[0];
2515 # State is equal for all types in class
2516 last;
2517 }
2518 }
2520 return $result;
2521 }
2523 sub resolve_fai_classes {
2524 my ($fai_base, $ldap_handle, $session_id) = @_;
2525 if (not defined $session_id) { $session_id = 0; }
2526 my $result;
2527 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2528 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2529 my $fai_classes;
2531 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2532 my $mesg= $ldap_handle->search(
2533 base => $fai_base,
2534 scope => 'sub',
2535 attrs => ['cn','objectClass','FAIstate'],
2536 filter => $fai_filter,
2537 );
2538 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2540 if($mesg->{'resultCode'} == 0 &&
2541 $mesg->count != 0) {
2542 foreach my $entry (@{$mesg->{entries}}) {
2543 if($entry->exists('cn')) {
2544 my $tmp_dn= $entry->dn();
2546 # Skip classname and ou dn parts for class
2547 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2549 # Skip classes without releases
2550 if((!defined($tmp_release)) || length($tmp_release)==0) {
2551 next;
2552 }
2554 my $tmp_cn= $entry->get_value('cn');
2555 my $tmp_state= $entry->get_value('FAIstate');
2557 my $tmp_type;
2558 # Get FAI type
2559 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2560 if(grep $_ eq $oclass, @possible_fai_classes) {
2561 $tmp_type= $oclass;
2562 last;
2563 }
2564 }
2566 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2567 # A Subrelease
2568 my @sub_releases = split(/,/, $tmp_release);
2570 # Walk through subreleases and build hash tree
2571 my $hash;
2572 while(my $tmp_sub_release = pop @sub_releases) {
2573 $hash .= "\{'$tmp_sub_release'\}->";
2574 }
2575 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2576 } else {
2577 # A branch, no subrelease
2578 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2579 }
2580 } elsif (!$entry->exists('cn')) {
2581 my $tmp_dn= $entry->dn();
2582 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2584 # Skip classes without releases
2585 if((!defined($tmp_release)) || length($tmp_release)==0) {
2586 next;
2587 }
2589 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2590 # A Subrelease
2591 my @sub_releases= split(/,/, $tmp_release);
2593 # Walk through subreleases and build hash tree
2594 my $hash;
2595 while(my $tmp_sub_release = pop @sub_releases) {
2596 $hash .= "\{'$tmp_sub_release'\}->";
2597 }
2598 # Remove the last two characters
2599 chop($hash);
2600 chop($hash);
2602 eval('$fai_classes->'.$hash.'= {}');
2603 } else {
2604 # A branch, no subrelease
2605 if(!exists($fai_classes->{$tmp_release})) {
2606 $fai_classes->{$tmp_release} = {};
2607 }
2608 }
2609 }
2610 }
2612 # The hash is complete, now we can honor the copy-on-write based missing entries
2613 foreach my $release (keys %$fai_classes) {
2614 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2615 }
2616 }
2617 return $result;
2618 }
2620 sub apply_fai_inheritance {
2621 my $fai_classes = shift || return {};
2622 my $tmp_classes;
2624 # Get the classes from the branch
2625 foreach my $class (keys %{$fai_classes}) {
2626 # Skip subreleases
2627 if($class =~ /^ou=.*$/) {
2628 next;
2629 } else {
2630 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2631 }
2632 }
2634 # Apply to each subrelease
2635 foreach my $subrelease (keys %{$fai_classes}) {
2636 if($subrelease =~ /ou=/) {
2637 foreach my $tmp_class (keys %{$tmp_classes}) {
2638 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2639 $fai_classes->{$subrelease}->{$tmp_class} =
2640 deep_copy($tmp_classes->{$tmp_class});
2641 } else {
2642 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2643 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2644 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2645 deep_copy($tmp_classes->{$tmp_class}->{$type});
2646 }
2647 }
2648 }
2649 }
2650 }
2651 }
2653 # Find subreleases in deeper levels
2654 foreach my $subrelease (keys %{$fai_classes}) {
2655 if($subrelease =~ /ou=/) {
2656 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2657 if($subsubrelease =~ /ou=/) {
2658 apply_fai_inheritance($fai_classes->{$subrelease});
2659 }
2660 }
2661 }
2662 }
2664 return $fai_classes;
2665 }
2667 sub get_fai_release_entries {
2668 my $tmp_classes = shift || return;
2669 my $parent = shift || "";
2670 my @result = shift || ();
2672 foreach my $entry (keys %{$tmp_classes}) {
2673 if(defined($entry)) {
2674 if($entry =~ /^ou=.*$/) {
2675 my $release_name = $entry;
2676 $release_name =~ s/ou=//g;
2677 if(length($parent)>0) {
2678 $release_name = $parent."/".$release_name;
2679 }
2680 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2681 foreach my $bufentry(@bufentries) {
2682 push @result, $bufentry;
2683 }
2684 } else {
2685 my @types = get_fai_types($tmp_classes->{$entry});
2686 foreach my $type (@types) {
2687 push @result,
2688 {
2689 'class' => $entry,
2690 'type' => $type->{'type'},
2691 'release' => $parent,
2692 'state' => $type->{'state'},
2693 };
2694 }
2695 }
2696 }
2697 }
2699 return @result;
2700 }
2702 sub deep_copy {
2703 my $this = shift;
2704 if (not ref $this) {
2705 $this;
2706 } elsif (ref $this eq "ARRAY") {
2707 [map deep_copy($_), @$this];
2708 } elsif (ref $this eq "HASH") {
2709 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2710 } else { die "what type is $_?" }
2711 }
2714 sub session_run_result {
2715 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2716 $kernel->sig(CHLD => "child_reap");
2717 }
2719 sub session_run_debug {
2720 my $result = $_[ARG0];
2721 print STDERR "$result\n";
2722 }
2724 sub session_run_done {
2725 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2726 delete $heap->{task}->{$task_id};
2727 }
2730 sub create_sources_list {
2731 my $session_id = shift;
2732 my $ldap_handle = &main::get_ldap_handle;
2733 my $result="/tmp/gosa_si_tmp_sources_list";
2735 # Remove old file
2736 if(stat($result)) {
2737 unlink($result);
2738 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2739 }
2741 my $fh;
2742 open($fh, ">$result");
2743 if (not defined $fh) {
2744 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2745 return undef;
2746 }
2747 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2748 my $mesg=$ldap_handle->search(
2749 base => $main::ldap_server_dn,
2750 scope => 'base',
2751 attrs => 'FAIrepository',
2752 filter => 'objectClass=FAIrepositoryServer'
2753 );
2754 if($mesg->count) {
2755 foreach my $entry(@{$mesg->{'entries'}}) {
2756 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2757 my ($server, $tag, $release, $sections)= split /\|/, $value;
2758 my $line = "deb $server $release";
2759 $sections =~ s/,/ /g;
2760 $line.= " $sections";
2761 print $fh $line."\n";
2762 }
2763 }
2764 }
2765 } else {
2766 if (defined $main::ldap_server_dn){
2767 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2768 } else {
2769 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2770 }
2771 }
2772 close($fh);
2774 return $result;
2775 }
2778 sub run_create_packages_list_db {
2779 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2780 my $session_id = $session->ID;
2782 my $task = POE::Wheel::Run->new(
2783 Priority => +20,
2784 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2785 StdoutEvent => "session_run_result",
2786 StderrEvent => "session_run_debug",
2787 CloseEvent => "session_run_done",
2788 );
2789 $heap->{task}->{ $task->ID } = $task;
2790 }
2793 sub create_packages_list_db {
2794 my ($ldap_handle, $sources_file, $session_id) = @_;
2796 # it should not be possible to trigger a recreation of packages_list_db
2797 # while packages_list_db is under construction, so set flag packages_list_under_construction
2798 # which is tested befor recreation can be started
2799 if (-r $packages_list_under_construction) {
2800 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2801 return;
2802 } else {
2803 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2804 # set packages_list_under_construction to true
2805 system("touch $packages_list_under_construction");
2806 @packages_list_statements=();
2807 }
2809 if (not defined $session_id) { $session_id = 0; }
2810 if (not defined $ldap_handle) {
2811 $ldap_handle= &get_ldap_handle();
2813 if (not defined $ldap_handle) {
2814 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2815 unlink($packages_list_under_construction);
2816 return;
2817 }
2818 }
2819 if (not defined $sources_file) {
2820 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2821 $sources_file = &create_sources_list($session_id);
2822 }
2824 if (not defined $sources_file) {
2825 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2826 unlink($packages_list_under_construction);
2827 return;
2828 }
2830 my $line;
2832 open(CONFIG, "<$sources_file") or do {
2833 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2834 unlink($packages_list_under_construction);
2835 return;
2836 };
2838 # Read lines
2839 while ($line = <CONFIG>){
2840 # Unify
2841 chop($line);
2842 $line =~ s/^\s+//;
2843 $line =~ s/^\s+/ /;
2845 # Strip comments
2846 $line =~ s/#.*$//g;
2848 # Skip empty lines
2849 if ($line =~ /^\s*$/){
2850 next;
2851 }
2853 # Interpret deb line
2854 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2855 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2856 my $section;
2857 foreach $section (split(' ', $sections)){
2858 &parse_package_info( $baseurl, $dist, $section, $session_id );
2859 }
2860 }
2861 }
2863 close (CONFIG);
2865 if(keys(%repo_dirs)) {
2866 find(\&cleanup_and_extract, keys( %repo_dirs ));
2867 &main::strip_packages_list_statements();
2868 $packages_list_db->exec_statementlist(\@packages_list_statements);
2869 }
2870 unlink($packages_list_under_construction);
2871 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2872 return;
2873 }
2875 # This function should do some intensive task to minimize the db-traffic
2876 sub strip_packages_list_statements {
2877 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2878 my @new_statement_list=();
2879 my $hash;
2880 my $insert_hash;
2881 my $update_hash;
2882 my $delete_hash;
2883 my $known_packages_hash;
2884 my $local_timestamp=get_time();
2886 foreach my $existing_entry (@existing_entries) {
2887 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2888 }
2890 foreach my $statement (@packages_list_statements) {
2891 if($statement =~ /^INSERT/i) {
2892 # Assign the values from the insert statement
2893 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2894 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2895 if(exists($hash->{$distribution}->{$package}->{$version})) {
2896 # If section or description has changed, update the DB
2897 if(
2898 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2899 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2900 ) {
2901 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2902 } else {
2903 # package is already present in database. cache this knowledge for later use
2904 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2905 }
2906 } else {
2907 # Insert a non-existing entry to db
2908 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2909 }
2910 } elsif ($statement =~ /^UPDATE/i) {
2911 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2912 /^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;
2913 foreach my $distribution (keys %{$hash}) {
2914 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2915 # update the insertion hash to execute only one query per package (insert instead insert+update)
2916 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2917 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2918 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2919 my $section;
2920 my $description;
2921 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2922 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2923 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2924 }
2925 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2926 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2927 }
2928 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2929 }
2930 }
2931 }
2932 }
2933 }
2935 # Check for orphaned entries
2936 foreach my $existing_entry (@existing_entries) {
2937 my $distribution= @{$existing_entry}[0];
2938 my $package= @{$existing_entry}[1];
2939 my $version= @{$existing_entry}[2];
2940 my $section= @{$existing_entry}[3];
2942 if(
2943 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2944 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2945 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2946 ) {
2947 next;
2948 } else {
2949 # Insert entry to delete hash
2950 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2951 }
2952 }
2954 # unroll the insert hash
2955 foreach my $distribution (keys %{$insert_hash}) {
2956 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2957 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2958 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2959 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2960 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2961 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2962 ."'$local_timestamp')";
2963 }
2964 }
2965 }
2967 # unroll the update hash
2968 foreach my $distribution (keys %{$update_hash}) {
2969 foreach my $package (keys %{$update_hash->{$distribution}}) {
2970 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2971 my $set = "";
2972 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2973 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2974 }
2975 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2976 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2977 }
2978 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2979 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2980 }
2981 if(defined($set) and length($set) > 0) {
2982 $set .= "timestamp = '$local_timestamp'";
2983 } else {
2984 next;
2985 }
2986 push @new_statement_list,
2987 "UPDATE $main::packages_list_tn SET $set WHERE"
2988 ." distribution = '$distribution'"
2989 ." AND package = '$package'"
2990 ." AND version = '$version'";
2991 }
2992 }
2993 }
2995 # unroll the delete hash
2996 foreach my $distribution (keys %{$delete_hash}) {
2997 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2998 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2999 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3000 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3001 }
3002 }
3003 }
3005 @packages_list_statements = @new_statement_list;
3006 }
3009 sub parse_package_info {
3010 my ($baseurl, $dist, $section, $session_id)= @_;
3011 my ($package);
3012 if (not defined $session_id) { $session_id = 0; }
3013 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3014 $repo_dirs{ "${repo_path}/pool" } = 1;
3016 foreach $package ("Packages.gz"){
3017 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3018 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3019 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3020 }
3022 }
3025 sub get_package {
3026 my ($url, $dest, $session_id)= @_;
3027 if (not defined $session_id) { $session_id = 0; }
3029 my $tpath = dirname($dest);
3030 -d "$tpath" || mkpath "$tpath";
3032 # This is ugly, but I've no time to take a look at "how it works in perl"
3033 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3034 system("gunzip -cd '$dest' > '$dest.in'");
3035 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3036 unlink($dest);
3037 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3038 } else {
3039 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
3040 }
3041 return 0;
3042 }
3045 sub parse_package {
3046 my ($path, $dist, $srv_path, $session_id)= @_;
3047 if (not defined $session_id) { $session_id = 0;}
3048 my ($package, $version, $section, $description);
3049 my $PACKAGES;
3050 my $timestamp = &get_time();
3052 if(not stat("$path.in")) {
3053 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3054 return;
3055 }
3057 open($PACKAGES, "<$path.in");
3058 if(not defined($PACKAGES)) {
3059 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3060 return;
3061 }
3063 # Read lines
3064 while (<$PACKAGES>){
3065 my $line = $_;
3066 # Unify
3067 chop($line);
3069 # Use empty lines as a trigger
3070 if ($line =~ /^\s*$/){
3071 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3072 push(@packages_list_statements, $sql);
3073 $package = "none";
3074 $version = "none";
3075 $section = "none";
3076 $description = "none";
3077 next;
3078 }
3080 # Trigger for package name
3081 if ($line =~ /^Package:\s/){
3082 ($package)= ($line =~ /^Package: (.*)$/);
3083 next;
3084 }
3086 # Trigger for version
3087 if ($line =~ /^Version:\s/){
3088 ($version)= ($line =~ /^Version: (.*)$/);
3089 next;
3090 }
3092 # Trigger for description
3093 if ($line =~ /^Description:\s/){
3094 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3095 next;
3096 }
3098 # Trigger for section
3099 if ($line =~ /^Section:\s/){
3100 ($section)= ($line =~ /^Section: (.*)$/);
3101 next;
3102 }
3104 # Trigger for filename
3105 if ($line =~ /^Filename:\s/){
3106 my ($filename) = ($line =~ /^Filename: (.*)$/);
3107 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3108 next;
3109 }
3110 }
3112 close( $PACKAGES );
3113 unlink( "$path.in" );
3114 }
3117 sub store_fileinfo {
3118 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3120 my %fileinfo = (
3121 'package' => $package,
3122 'dist' => $dist,
3123 'version' => $vers,
3124 );
3126 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3127 }
3130 sub cleanup_and_extract {
3131 my $fileinfo = $repo_files{ $File::Find::name };
3133 if( defined $fileinfo ) {
3134 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3135 my $sql;
3136 my $package = $fileinfo->{ 'package' };
3137 my $newver = $fileinfo->{ 'version' };
3139 mkpath($dir);
3140 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3142 if( -f "$dir/DEBIAN/templates" ) {
3144 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3146 my $tmpl= ""; {
3147 local $/=undef;
3148 open FILE, "$dir/DEBIAN/templates";
3149 $tmpl = &encode_base64(<FILE>);
3150 close FILE;
3151 }
3152 rmtree("$dir/DEBIAN/templates");
3154 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3155 push @packages_list_statements, $sql;
3156 }
3157 }
3159 return;
3160 }
3163 sub register_at_foreign_servers {
3164 my ($kernel) = $_[KERNEL];
3166 # hole alle bekannten server aus known_server_db
3167 my $server_sql = "SELECT * FROM $known_server_tn";
3168 my $server_res = $known_server_db->exec_statement($server_sql);
3170 # no entries in known_server_db
3171 if (not ref(@$server_res[0]) eq "ARRAY") {
3172 # TODO
3173 }
3175 # detect already connected clients
3176 my $client_sql = "SELECT * FROM $known_clients_tn";
3177 my $client_res = $known_clients_db->exec_statement($client_sql);
3179 # send my server details to all other gosa-si-server within the network
3180 foreach my $hit (@$server_res) {
3181 my $hostname = @$hit[0];
3182 my $hostkey = &create_passwd;
3184 # add already connected clients to registration message
3185 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3186 &add_content2xml_hash($myhash, 'key', $hostkey);
3187 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3189 # add locally loaded gosa-si modules to registration message
3190 my $loaded_modules = {};
3191 while (my ($package, $pck_info) = each %$known_modules) {
3192 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3193 foreach my $act_module (keys(%{@$pck_info[2]})) {
3194 $loaded_modules->{$act_module} = "";
3195 }
3196 }
3198 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3200 # add macaddress to registration message
3201 my ($host_ip, $host_port) = split(/:/, $hostname);
3202 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3203 my $network_interface= &get_interface_for_ip($local_ip);
3204 my $host_mac = &get_mac_for_interface($network_interface);
3205 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3207 # build registration message and send it
3208 my $foreign_server_msg = &create_xml_string($myhash);
3209 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3210 }
3212 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3213 return;
3214 }
3217 #==== MAIN = main ==============================================================
3218 # parse commandline options
3219 Getopt::Long::Configure( "bundling" );
3220 GetOptions("h|help" => \&usage,
3221 "c|config=s" => \$cfg_file,
3222 "f|foreground" => \$foreground,
3223 "v|verbose+" => \$verbose,
3224 "no-arp+" => \$no_arp,
3225 );
3227 # read and set config parameters
3228 &check_cmdline_param ;
3229 &read_configfile($cfg_file, %cfg_defaults);
3230 &check_pid;
3232 $SIG{CHLD} = 'IGNORE';
3234 # forward error messages to logfile
3235 if( ! $foreground ) {
3236 open( STDIN, '+>/dev/null' );
3237 open( STDOUT, '+>&STDIN' );
3238 open( STDERR, '+>&STDIN' );
3239 }
3241 # Just fork, if we are not in foreground mode
3242 if( ! $foreground ) {
3243 chdir '/' or die "Can't chdir to /: $!";
3244 $pid = fork;
3245 setsid or die "Can't start a new session: $!";
3246 umask 0;
3247 } else {
3248 $pid = $$;
3249 }
3251 # Do something useful - put our PID into the pid_file
3252 if( 0 != $pid ) {
3253 open( LOCK_FILE, ">$pid_file" );
3254 print LOCK_FILE "$pid\n";
3255 close( LOCK_FILE );
3256 if( !$foreground ) {
3257 exit( 0 )
3258 };
3259 }
3261 # parse head url and revision from svn
3262 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3263 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3264 $server_headURL = defined $1 ? $1 : 'unknown' ;
3265 $server_revision = defined $2 ? $2 : 'unknown' ;
3266 if ($server_headURL =~ /\/tag\// ||
3267 $server_headURL =~ /\/branches\// ) {
3268 $server_status = "stable";
3269 } else {
3270 $server_status = "developmental" ;
3271 }
3273 # Prepare log file
3274 $root_uid = getpwnam('root');
3275 $adm_gid = getgrnam('adm');
3276 chmod(0640, $log_file);
3277 chown($root_uid, $adm_gid, $log_file);
3278 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3280 daemon_log(" ", 1);
3281 daemon_log("$0 started!", 1);
3282 daemon_log("status: $server_status", 1);
3283 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3285 {
3286 no strict "refs";
3288 if ($db_module eq "DBmysql") {
3289 # connect to incoming_db
3290 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3292 # connect to gosa-si job queue
3293 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3295 # connect to known_clients_db
3296 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3298 # connect to foreign_clients_db
3299 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3301 # connect to known_server_db
3302 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3304 # connect to login_usr_db
3305 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3307 # connect to fai_server_db
3308 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3310 # connect to fai_release_db
3311 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3313 # connect to packages_list_db
3314 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3316 # connect to messaging_db
3317 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3319 } elsif ($db_module eq "DBsqlite") {
3320 # connect to incoming_db
3321 unlink($incoming_file_name);
3322 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3324 # connect to gosa-si job queue
3325 unlink($job_queue_file_name); ## just for debugging
3326 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3327 chmod(0660, $job_queue_file_name);
3328 chown($root_uid, $adm_gid, $job_queue_file_name);
3330 # connect to known_clients_db
3331 unlink($known_clients_file_name); ## just for debugging
3332 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3333 chmod(0660, $known_clients_file_name);
3334 chown($root_uid, $adm_gid, $known_clients_file_name);
3336 # connect to foreign_clients_db
3337 unlink($foreign_clients_file_name);
3338 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3339 chmod(0660, $foreign_clients_file_name);
3340 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3342 # connect to known_server_db
3343 unlink($known_server_file_name);
3344 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3345 chmod(0660, $known_server_file_name);
3346 chown($root_uid, $adm_gid, $known_server_file_name);
3348 # connect to login_usr_db
3349 unlink($login_users_file_name);
3350 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3351 chmod(0660, $login_users_file_name);
3352 chown($root_uid, $adm_gid, $login_users_file_name);
3354 # connect to fai_server_db
3355 unlink($fai_server_file_name);
3356 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3357 chmod(0660, $fai_server_file_name);
3358 chown($root_uid, $adm_gid, $fai_server_file_name);
3360 # connect to fai_release_db
3361 unlink($fai_release_file_name);
3362 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3363 chmod(0660, $fai_release_file_name);
3364 chown($root_uid, $adm_gid, $fai_release_file_name);
3366 # connect to packages_list_db
3367 #unlink($packages_list_file_name);
3368 unlink($packages_list_under_construction);
3369 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3370 chmod(0660, $packages_list_file_name);
3371 chown($root_uid, $adm_gid, $packages_list_file_name);
3373 # connect to messaging_db
3374 unlink($messaging_file_name);
3375 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3376 chmod(0660, $messaging_file_name);
3377 chown($root_uid, $adm_gid, $messaging_file_name);
3378 }
3379 }
3381 # Creating tables
3382 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3383 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3384 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3385 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3386 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3387 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3388 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3389 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3390 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3391 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3394 # create xml object used for en/decrypting
3395 $xml = new XML::Simple();
3398 # foreign servers
3399 my @foreign_server_list;
3401 # add foreign server from cfg file
3402 if ($foreign_server_string ne "") {
3403 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3404 foreach my $foreign_server (@cfg_foreign_server_list) {
3405 push(@foreign_server_list, $foreign_server);
3406 }
3408 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3409 }
3411 # Perform a DNS lookup for server registration if flag is true
3412 if ($dns_lookup eq "true") {
3413 # Add foreign server from dns
3414 my @tmp_servers;
3415 if (not $server_domain) {
3416 # Try our DNS Searchlist
3417 for my $domain(get_dns_domains()) {
3418 chomp($domain);
3419 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3420 if(@$tmp_domains) {
3421 for my $tmp_server(@$tmp_domains) {
3422 push @tmp_servers, $tmp_server;
3423 }
3424 }
3425 }
3426 if(@tmp_servers && length(@tmp_servers)==0) {
3427 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3428 }
3429 } else {
3430 @tmp_servers = &get_server_addresses($server_domain);
3431 if( 0 == @tmp_servers ) {
3432 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3433 }
3434 }
3436 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3438 foreach my $server (@tmp_servers) {
3439 unshift(@foreign_server_list, $server);
3440 }
3441 } else {
3442 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3443 }
3446 # eliminate duplicate entries
3447 @foreign_server_list = &del_doubles(@foreign_server_list);
3448 my $all_foreign_server = join(", ", @foreign_server_list);
3449 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3451 # add all found foreign servers to known_server
3452 my $act_timestamp = &get_time();
3453 foreach my $foreign_server (@foreign_server_list) {
3455 # do not add myself to known_server_db
3456 if (&is_local($foreign_server)) { next; }
3457 ######################################
3459 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3460 primkey=>['hostname'],
3461 hostname=>$foreign_server,
3462 macaddress=>"",
3463 status=>'not_jet_registered',
3464 hostkey=>"none",
3465 loaded_modules => "none",
3466 timestamp=>$act_timestamp,
3467 } );
3468 }
3471 # Import all modules
3472 &import_modules;
3474 # Check wether all modules are gosa-si valid passwd check
3475 &password_check;
3477 # Prepare for using Opsi
3478 if ($opsi_enabled eq "true") {
3479 use JSON::RPC::Client;
3480 use XML::Quote qw(:all);
3481 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3482 $opsi_client = new JSON::RPC::Client;
3483 }
3486 POE::Component::Server::TCP->new(
3487 Alias => "TCP_SERVER",
3488 Port => $server_port,
3489 ClientInput => sub {
3490 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3491 my $session_id = $session->ID;
3492 my $remote_ip = $heap->{'remote_ip'};
3493 push(@msgs_to_decrypt, $input);
3494 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3495 $kernel->yield("msg_to_decrypt");
3496 },
3497 InlineStates => {
3498 msg_to_decrypt => \&msg_to_decrypt,
3499 next_task => \&next_task,
3500 task_result => \&handle_task_result,
3501 task_done => \&handle_task_done,
3502 task_debug => \&handle_task_debug,
3503 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3504 }
3505 );
3507 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3509 # create session for repeatedly checking the job queue for jobs
3510 POE::Session->create(
3511 inline_states => {
3512 _start => \&session_start,
3513 register_at_foreign_servers => \®ister_at_foreign_servers,
3514 sig_handler => \&sig_handler,
3515 next_task => \&next_task,
3516 task_result => \&handle_task_result,
3517 task_done => \&handle_task_done,
3518 task_debug => \&handle_task_debug,
3519 watch_for_next_tasks => \&watch_for_next_tasks,
3520 watch_for_new_messages => \&watch_for_new_messages,
3521 watch_for_delivery_messages => \&watch_for_delivery_messages,
3522 watch_for_done_messages => \&watch_for_done_messages,
3523 watch_for_new_jobs => \&watch_for_new_jobs,
3524 watch_for_modified_jobs => \&watch_for_modified_jobs,
3525 watch_for_done_jobs => \&watch_for_done_jobs,
3526 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3527 watch_for_old_known_clients => \&watch_for_old_known_clients,
3528 create_packages_list_db => \&run_create_packages_list_db,
3529 create_fai_server_db => \&run_create_fai_server_db,
3530 create_fai_release_db => \&run_create_fai_release_db,
3531 recreate_packages_db => \&run_recreate_packages_db,
3532 session_run_result => \&session_run_result,
3533 session_run_debug => \&session_run_debug,
3534 session_run_done => \&session_run_done,
3535 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3536 }
3537 );
3540 POE::Kernel->run();
3541 exit;