a5712ae307e9c37f23f2ccfee543f652e1e35fe7
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 #===============================================================================
23 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
25 use strict;
26 use warnings;
27 use Getopt::Long;
28 use Config::IniFiles;
29 use POSIX;
31 use Fcntl qw/:flock/;
32 use IO::Socket::INET;
33 use IO::Handle;
34 use IO::Select;
35 use Symbol qw(qualify_to_ref);
36 use Crypt::Rijndael;
37 use MIME::Base64;
38 use Digest::MD5 qw(md5 md5_hex md5_base64);
39 use XML::Simple;
40 use Data::Dumper;
41 use Sys::Syslog qw( :DEFAULT setlogsock);
42 use Time::HiRes qw( usleep);
43 use Cwd;
44 use File::Spec;
45 use File::Basename;
46 use File::Find;
47 use File::Copy;
48 use File::Path;
49 use GOSA::GosaSupportDaemon;
50 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
51 use Net::LDAP;
52 use Net::LDAP::Util qw(:escape);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
59 our $verbose= 0;
61 my $db_module = "DBsqlite";
62 {
63 no strict "refs";
64 require ("GOSA/".$db_module.".pm");
65 ("GOSA/".$db_module)->import;
66 daemon_log("0 INFO: importing database module '$db_module'", 1);
67 }
69 my $modules_path = "/usr/lib/gosa-si/modules";
70 use lib "/usr/lib/gosa-si/modules";
72 our $global_kernel;
73 my ($foreground, $ping_timeout);
74 my ($server);
75 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
76 my ($messaging_db_loop_delay);
77 my ($procid, $pid);
78 my $arp_fifo;
79 my ($xml);
80 my $sources_list;
81 my $max_clients;
82 my %repo_files=();
83 my $repo_path;
84 my %repo_dirs=();
86 # Variables declared in config file are always set to 'our'
87 our (%cfg_defaults, $log_file, $pid_file,
88 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
89 $arp_activ, $gosa_unit_tag,
90 $GosaPackages_key, $gosa_timeout,
91 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
92 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
93 $arp_enabled, $arp_interface,
94 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
95 $new_systems_ou,
96 );
98 # additional variable which should be globaly accessable
99 our $server_address;
100 our $server_mac_address;
101 our $gosa_address;
102 our $no_arp;
103 our $forground;
104 our $cfg_file;
105 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version);
106 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
107 our $known_modules;
108 our $root_uid;
109 our $adm_gid;
111 # if foreground is not null, script will be not forked to background
112 $foreground = 0 ;
114 # specifies the timeout seconds while checking the online status of a registrating client
115 $ping_timeout = 5;
117 $no_arp = 0;
118 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
119 my @packages_list_statements;
120 my $watch_for_new_jobs_in_progress = 0;
122 # holds all incoming decrypted messages
123 our $incoming_db;
124 our $incoming_tn = 'incoming';
125 my $incoming_file_name;
126 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
127 "timestamp VARCHAR(14) DEFAULT 'none'",
128 "headertag VARCHAR(255) DEFAULT 'none'",
129 "targettag VARCHAR(255) DEFAULT 'none'",
130 "xmlmessage TEXT",
131 "module VARCHAR(255) DEFAULT 'none'",
132 "sessionid VARCHAR(255) DEFAULT '0'",
133 );
135 # holds all gosa jobs
136 our $job_db;
137 our $job_queue_tn = 'jobs';
138 my $job_queue_file_name;
139 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
140 "timestamp VARCHAR(14) DEFAULT 'none'",
141 "status VARCHAR(255) DEFAULT 'none'",
142 "result TEXT",
143 "progress VARCHAR(255) DEFAULT 'none'",
144 "headertag VARCHAR(255) DEFAULT 'none'",
145 "targettag VARCHAR(255) DEFAULT 'none'",
146 "xmlmessage TEXT",
147 "macaddress VARCHAR(17) DEFAULT 'none'",
148 "plainname VARCHAR(255) DEFAULT 'none'",
149 "siserver VARCHAR(255) DEFAULT 'none'",
150 "modified INTEGER DEFAULT '0'",
151 );
153 # holds all other gosa-si-server
154 our $known_server_db;
155 our $known_server_tn = "known_server";
156 my $known_server_file_name;
157 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
159 # holds all registrated clients
160 our $known_clients_db;
161 our $known_clients_tn = "known_clients";
162 my $known_clients_file_name;
163 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
165 # holds all registered clients at a foreign server
166 our $foreign_clients_db;
167 our $foreign_clients_tn = "foreign_clients";
168 my $foreign_clients_file_name;
169 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
171 # holds all logged in user at each client
172 our $login_users_db;
173 our $login_users_tn = "login_users";
174 my $login_users_file_name;
175 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
177 # holds all fai server, the debian release and tag
178 our $fai_server_db;
179 our $fai_server_tn = "fai_server";
180 my $fai_server_file_name;
181 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
183 our $fai_release_db;
184 our $fai_release_tn = "fai_release";
185 my $fai_release_file_name;
186 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
188 # holds all packages available from different repositories
189 our $packages_list_db;
190 our $packages_list_tn = "packages_list";
191 my $packages_list_file_name;
192 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
193 my $outdir = "/tmp/packages_list_db";
194 my $arch = "i386";
196 # holds all messages which should be delivered to a user
197 our $messaging_db;
198 our $messaging_tn = "messaging";
199 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
200 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
201 my $messaging_file_name;
203 # path to directory to store client install log files
204 our $client_fai_log_dir = "/var/log/fai";
206 # queue which stores taskes until one of the $max_children children are ready to process the task
207 #my @tasks = qw();
208 my @msgs_to_decrypt = qw();
209 my $max_children = 2;
212 # loop delay for job queue to look for opsi jobs
213 my $job_queue_opsi_delay = 10;
214 our $opsi_client;
215 our $opsi_url;
217 # Lifetime of logged in user information. If no update information comes after n seconds,
218 # the user is expeceted to be no longer logged in or the host is no longer running. Because
219 # of this, the user is deleted from login_users_db
220 our $logged_in_user_date_of_expiry = 600;
223 %cfg_defaults = (
224 "general" => {
225 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
226 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
227 },
228 "server" => {
229 "ip" => [\$server_ip, "0.0.0.0"],
230 "port" => [\$server_port, "20081"],
231 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
232 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
233 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
234 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
235 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
236 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
237 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
238 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
239 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
240 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
241 "repo-path" => [\$repo_path, '/srv/www/repository'],
242 "ldap-uri" => [\$ldap_uri, ""],
243 "ldap-base" => [\$ldap_base, ""],
244 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
245 "ldap-admin-password" => [\$ldap_admin_password, ""],
246 "ldap-version" => [\$ldap_version, 3],
247 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
248 "max-clients" => [\$max_clients, 10],
249 "wol-password" => [\$wake_on_lan_passwd, ""],
250 "mysql-username" => [\$mysql_username, "gosa_si"],
251 "mysql-password" => [\$mysql_password, ""],
252 "mysql-database" => [\$mysql_database, "gosa_si"],
253 "mysql-host" => [\$mysql_host, "127.0.0.1"],
254 },
255 "GOsaPackages" => {
256 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
257 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
258 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
259 "key" => [\$GosaPackages_key, "none"],
260 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
261 },
262 "ClientPackages" => {
263 "key" => [\$ClientPackages_key, "none"],
264 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
265 },
266 "ServerPackages"=> {
267 "address" => [\$foreign_server_string, ""],
268 "dns-lookup" => [\$dns_lookup, "true"],
269 "domain" => [\$server_domain, ""],
270 "key" => [\$ServerPackages_key, "none"],
271 "key-lifetime" => [\$foreign_servers_register_delay, 120],
272 "job-synchronization-enabled" => [\$job_synchronization, "true"],
273 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
274 },
275 "ArpHandler" => {
276 "enabled" => [\$arp_enabled, "true"],
277 "interface" => [\$arp_interface, "all"],
278 },
279 "Opsi" => {
280 "enabled" => [\$opsi_enabled, "false"],
281 "server" => [\$opsi_server, "localhost"],
282 "admin" => [\$opsi_admin, "opsi-admin"],
283 "password" => [\$opsi_password, "secret"],
284 },
286 );
289 #=== FUNCTION ================================================================
290 # NAME: usage
291 # PARAMETERS: nothing
292 # RETURNS: nothing
293 # DESCRIPTION: print out usage text to STDERR
294 #===============================================================================
295 sub usage {
296 print STDERR << "EOF" ;
297 usage: $prg [-hvf] [-c config]
299 -h : this (help) message
300 -c <file> : config file
301 -f : foreground, process will not be forked to background
302 -v : be verbose (multiple to increase verbosity)
303 -no-arp : starts $prg without connection to arp module
305 EOF
306 print "\n" ;
307 }
310 #=== FUNCTION ================================================================
311 # NAME: logging
312 # PARAMETERS: level - string - default 'info'
313 # msg - string -
314 # facility - string - default 'LOG_DAEMON'
315 # RETURNS: nothing
316 # DESCRIPTION: function for logging
317 #===============================================================================
318 sub daemon_log {
319 # log into log_file
320 my( $msg, $level ) = @_;
321 if(not defined $msg) { return }
322 if(not defined $level) { $level = 1 }
323 if($level > $verbose) { return }
324 if(defined $log_file){
325 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
326 if(not $open_log_fh) {
327 print STDERR "cannot open $log_file: $!";
328 return;
329 }
330 # check owner and group of log_file and update settings if necessary
331 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
332 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
333 chown($root_uid, $adm_gid, $log_file);
334 }
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 flock(LOG_HANDLE, LOCK_EX);
352 seek(LOG_HANDLE, 0, 2);
353 print LOG_HANDLE $log_msg;
354 flock(LOG_HANDLE, LOCK_UN);
355 if( $foreground ) {
356 print STDERR $log_msg;
357 }
358 }
359 close( LOG_HANDLE );
360 }
361 }
364 #=== FUNCTION ================================================================
365 # NAME: check_cmdline_param
366 # PARAMETERS: nothing
367 # RETURNS: nothing
368 # DESCRIPTION: validates commandline parameter
369 #===============================================================================
370 sub check_cmdline_param () {
371 my $err_config;
372 my $err_counter = 0;
373 if(not defined($cfg_file)) {
374 $cfg_file = "/etc/gosa-si/server.conf";
375 if(! -r $cfg_file) {
376 $err_config = "please specify a config file";
377 $err_counter += 1;
378 }
379 }
380 if( $err_counter > 0 ) {
381 &usage( "", 1 );
382 if( defined( $err_config)) { print STDERR "$err_config\n"}
383 print STDERR "\n";
384 exit( -1 );
385 }
386 }
389 #=== FUNCTION ================================================================
390 # NAME: check_pid
391 # PARAMETERS: nothing
392 # RETURNS: nothing
393 # DESCRIPTION: handels pid processing
394 #===============================================================================
395 sub check_pid {
396 $pid = -1;
397 # Check, if we are already running
398 if( open(LOCK_FILE, "<$pid_file") ) {
399 $pid = <LOCK_FILE>;
400 if( defined $pid ) {
401 chomp( $pid );
402 if( -f "/proc/$pid/stat" ) {
403 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
404 if( $stat ) {
405 print STDERR "\nERROR: Already running!\n";
406 close( LOCK_FILE );
407 exit -1;
408 }
409 }
410 }
411 close( LOCK_FILE );
412 unlink( $pid_file );
413 }
415 # create a syslog msg if it is not to possible to open PID file
416 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
417 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
418 if (open(LOCK_FILE, '<', $pid_file)
419 && ($pid = <LOCK_FILE>))
420 {
421 chomp($pid);
422 $msg .= "(PID $pid)\n";
423 } else {
424 $msg .= "(unable to read PID)\n";
425 }
426 if( ! ($foreground) ) {
427 openlog( $0, "cons,pid", "daemon" );
428 syslog( "warning", $msg );
429 closelog();
430 }
431 else {
432 print( STDERR " $msg " );
433 }
434 exit( -1 );
435 }
436 }
438 #=== FUNCTION ================================================================
439 # NAME: import_modules
440 # PARAMETERS: module_path - string - abs. path to the directory the modules
441 # are stored
442 # RETURNS: nothing
443 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
444 # state is on is imported by "require 'file';"
445 #===============================================================================
446 sub import_modules {
447 if (not -e $modules_path) {
448 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
449 }
451 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
453 while (defined (my $file = readdir (DIR))) {
454 if (not $file =~ /(\S*?).pm$/) {
455 next;
456 }
457 my $mod_name = $1;
459 # ArpHandler switch
460 if( $file =~ /ArpHandler.pm/ ) {
461 if( $arp_enabled eq "false" ) { next; }
462 }
464 eval { require $file; };
465 if ($@) {
466 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
467 daemon_log("$@", 1);
468 exit;
469 } else {
470 my $info = eval($mod_name.'::get_module_info()');
471 # Only load module if get_module_info() returns a non-null object
472 if( $info ) {
473 my ($input_address, $input_key, $event_hash) = @{$info};
474 $known_modules->{$mod_name} = $info;
475 daemon_log("0 INFO: module $mod_name loaded", 5);
476 }
477 }
478 }
479 close (DIR);
480 }
482 #=== FUNCTION ================================================================
483 # NAME: password_check
484 # PARAMETERS: nothing
485 # RETURNS: nothing
486 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
487 # the same password
488 #===============================================================================
489 sub password_check {
490 my $passwd_hash = {};
491 while (my ($mod_name, $mod_info) = each %$known_modules) {
492 my $mod_passwd = @$mod_info[1];
493 if (not defined $mod_passwd) { next; }
494 if (not exists $passwd_hash->{$mod_passwd}) {
495 $passwd_hash->{$mod_passwd} = $mod_name;
497 # escalates critical error
498 } else {
499 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
500 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
501 exit( -1 );
502 }
503 }
505 }
508 #=== FUNCTION ================================================================
509 # NAME: sig_int_handler
510 # PARAMETERS: signal - string - signal arose from system
511 # RETURNS: nothing
512 # DESCRIPTION: handels tasks to be done befor signal becomes active
513 #===============================================================================
514 sub sig_int_handler {
515 my ($signal) = @_;
517 # if (defined($ldap_handle)) {
518 # $ldap_handle->disconnect;
519 # }
520 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
523 daemon_log("shutting down gosa-si-server", 1);
524 system("kill `ps -C gosa-si-server -o pid=`");
525 }
526 $SIG{INT} = \&sig_int_handler;
529 sub check_key_and_xml_validity {
530 my ($crypted_msg, $module_key, $session_id) = @_;
531 my $msg;
532 my $msg_hash;
533 my $error_string;
534 eval{
535 $msg = &decrypt_msg($crypted_msg, $module_key);
537 if ($msg =~ /<xml>/i){
538 $msg =~ s/\s+/ /g; # just for better daemon_log
539 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
540 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
542 ##############
543 # check header
544 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
545 my $header_l = $msg_hash->{'header'};
546 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
547 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
548 my $header = @{$header_l}[0];
549 if( 0 == length $header) { die 'empty string in header tag'; }
551 ##############
552 # check source
553 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
554 my $source_l = $msg_hash->{'source'};
555 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
556 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
557 my $source = @{$source_l}[0];
558 if( 0 == length $source) { die 'source error'; }
560 ##############
561 # check target
562 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
563 my $target_l = $msg_hash->{'target'};
564 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
565 }
566 };
567 if($@) {
568 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
569 $msg = undef;
570 $msg_hash = undef;
571 }
573 return ($msg, $msg_hash);
574 }
577 sub check_outgoing_xml_validity {
578 my ($msg, $session_id) = @_;
580 my $msg_hash;
581 eval{
582 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
584 ##############
585 # check header
586 my $header_l = $msg_hash->{'header'};
587 if( 1 != @{$header_l} ) {
588 die 'no or more than one headers specified';
589 }
590 my $header = @{$header_l}[0];
591 if( 0 == length $header) {
592 die 'header has length 0';
593 }
595 ##############
596 # check source
597 my $source_l = $msg_hash->{'source'};
598 if( 1 != @{$source_l} ) {
599 die 'no or more than 1 sources specified';
600 }
601 my $source = @{$source_l}[0];
602 if( 0 == length $source) {
603 die 'source has length 0';
604 }
606 # Check if source contains hostname instead of ip address
607 if($source =~ /^[a-z][a-z0-9\.]+:\d+$/i) {
608 my ($hostname,$port) = split(/:/, $source);
609 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
610 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
611 # Write ip address to $source variable
612 $source = "$ip_address:$port";
613 }
614 }
615 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
616 $source =~ /^GOSA$/i) {
617 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
618 }
620 ##############
621 # check target
622 my $target_l = $msg_hash->{'target'};
623 if( 0 == @{$target_l} ) {
624 die "no targets specified";
625 }
626 foreach my $target (@$target_l) {
627 if( 0 == length $target) {
628 die "target has length 0";
629 }
630 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
631 $target =~ /^GOSA$/i ||
632 $target =~ /^\*$/ ||
633 $target =~ /KNOWN_SERVER/i ||
634 $target =~ /JOBDB/i ||
635 $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 ){
636 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
637 }
638 }
639 };
640 if($@) {
641 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
642 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
643 $msg_hash = undef;
644 }
646 return ($msg_hash);
647 }
650 sub input_from_known_server {
651 my ($input, $remote_ip, $session_id) = @_ ;
652 my ($msg, $msg_hash, $module);
654 my $sql_statement= "SELECT * FROM known_server";
655 my $query_res = $known_server_db->select_dbentry( $sql_statement );
657 while( my ($hit_num, $hit) = each %{ $query_res } ) {
658 my $host_name = $hit->{hostname};
659 if( not $host_name =~ "^$remote_ip") {
660 next;
661 }
662 my $host_key = $hit->{hostkey};
663 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
664 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
666 # check if module can open msg envelope with module key
667 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
668 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
669 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
670 daemon_log("$@", 8);
671 next;
672 }
673 else {
674 $msg = $tmp_msg;
675 $msg_hash = $tmp_msg_hash;
676 $module = "ServerPackages";
677 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
678 last;
679 }
680 }
682 if( (!$msg) || (!$msg_hash) || (!$module) ) {
683 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
684 }
686 return ($msg, $msg_hash, $module);
687 }
690 sub input_from_known_client {
691 my ($input, $remote_ip, $session_id) = @_ ;
692 my ($msg, $msg_hash, $module);
694 my $sql_statement= "SELECT * FROM known_clients";
695 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
696 while( my ($hit_num, $hit) = each %{ $query_res } ) {
697 my $host_name = $hit->{hostname};
698 if( not $host_name =~ /^$remote_ip:\d*$/) {
699 next;
700 }
701 my $host_key = $hit->{hostkey};
702 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
703 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
705 # check if module can open msg envelope with module key
706 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
708 if( (!$msg) || (!$msg_hash) ) {
709 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
710 &daemon_log("$@", 8);
711 next;
712 }
713 else {
714 $module = "ClientPackages";
715 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
716 last;
717 }
718 }
720 if( (!$msg) || (!$msg_hash) || (!$module) ) {
721 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
722 }
724 return ($msg, $msg_hash, $module);
725 }
728 sub input_from_unknown_host {
729 no strict "refs";
730 my ($input, $session_id) = @_ ;
731 my ($msg, $msg_hash, $module);
732 my $error_string;
734 my %act_modules = %$known_modules;
736 while( my ($mod, $info) = each(%act_modules)) {
738 # check a key exists for this module
739 my $module_key = ${$mod."_key"};
740 if( not defined $module_key ) {
741 if( $mod eq 'ArpHandler' ) {
742 next;
743 }
744 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
745 next;
746 }
747 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
749 # check if module can open msg envelope with module key
750 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
751 if( (not defined $msg) || (not defined $msg_hash) ) {
752 next;
753 } else {
754 $module = $mod;
755 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
756 last;
757 }
758 }
760 if( (!$msg) || (!$msg_hash) || (!$module)) {
761 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
762 }
764 return ($msg, $msg_hash, $module);
765 }
768 sub create_ciphering {
769 my ($passwd) = @_;
770 if((!defined($passwd)) || length($passwd)==0) {
771 $passwd = "";
772 }
773 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
774 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
775 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
776 $my_cipher->set_iv($iv);
777 return $my_cipher;
778 }
781 sub encrypt_msg {
782 my ($msg, $key) = @_;
783 my $my_cipher = &create_ciphering($key);
784 my $len;
785 {
786 use bytes;
787 $len= 16-length($msg)%16;
788 }
789 $msg = "\0"x($len).$msg;
790 $msg = $my_cipher->encrypt($msg);
791 chomp($msg = &encode_base64($msg));
792 # there are no newlines allowed inside msg
793 $msg=~ s/\n//g;
794 return $msg;
795 }
798 sub decrypt_msg {
800 my ($msg, $key) = @_ ;
801 $msg = &decode_base64($msg);
802 my $my_cipher = &create_ciphering($key);
803 $msg = $my_cipher->decrypt($msg);
804 $msg =~ s/\0*//g;
805 return $msg;
806 }
809 sub get_encrypt_key {
810 my ($target) = @_ ;
811 my $encrypt_key;
812 my $error = 0;
814 # target can be in known_server
815 if( not defined $encrypt_key ) {
816 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
817 my $query_res = $known_server_db->select_dbentry( $sql_statement );
818 while( my ($hit_num, $hit) = each %{ $query_res } ) {
819 my $host_name = $hit->{hostname};
820 if( $host_name ne $target ) {
821 next;
822 }
823 $encrypt_key = $hit->{hostkey};
824 last;
825 }
826 }
828 # target can be in known_client
829 if( not defined $encrypt_key ) {
830 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
831 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
832 while( my ($hit_num, $hit) = each %{ $query_res } ) {
833 my $host_name = $hit->{hostname};
834 if( $host_name ne $target ) {
835 next;
836 }
837 $encrypt_key = $hit->{hostkey};
838 last;
839 }
840 }
842 return $encrypt_key;
843 }
846 #=== FUNCTION ================================================================
847 # NAME: open_socket
848 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
849 # [PeerPort] string necessary if port not appended by PeerAddr
850 # RETURNS: socket IO::Socket::INET
851 # DESCRIPTION: open a socket to PeerAddr
852 #===============================================================================
853 sub open_socket {
854 my ($PeerAddr, $PeerPort) = @_ ;
855 if(defined($PeerPort)){
856 $PeerAddr = $PeerAddr.":".$PeerPort;
857 }
858 my $socket;
859 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
860 Porto => "tcp",
861 Type => SOCK_STREAM,
862 Timeout => 5,
863 );
864 if(not defined $socket) {
865 return;
866 }
867 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
868 return $socket;
869 }
872 sub send_msg_to_target {
873 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
874 my $error = 0;
875 my $header;
876 my $timestamp = &get_time();
877 my $new_status;
878 my $act_status;
879 my ($sql_statement, $res);
881 if( $msg_header ) {
882 $header = "'$msg_header'-";
883 } else {
884 $header = "";
885 }
887 # Patch the source ip
888 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
889 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
890 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
891 }
893 # encrypt xml msg
894 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
896 # opensocket
897 my $socket = &open_socket($address);
898 if( !$socket ) {
899 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
900 $error++;
901 }
903 if( $error == 0 ) {
904 # send xml msg
905 print $socket $crypted_msg."\n";
907 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
908 daemon_log("$session_id DEBUG: message:\n$msg", 9);
910 }
912 # close socket in any case
913 if( $socket ) {
914 close $socket;
915 }
917 if( $error > 0 ) { $new_status = "down"; }
918 else { $new_status = $msg_header; }
921 # known_clients
922 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
923 $res = $known_clients_db->select_dbentry($sql_statement);
924 if( keys(%$res) == 1) {
925 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
926 if ($act_status eq "down" && $new_status eq "down") {
927 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
928 $res = $known_clients_db->del_dbentry($sql_statement);
929 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
930 } else {
931 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
932 $res = $known_clients_db->update_dbentry($sql_statement);
933 if($new_status eq "down"){
934 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
935 } else {
936 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
937 }
938 }
939 }
941 # known_server
942 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
943 $res = $known_server_db->select_dbentry($sql_statement);
944 if( keys(%$res) == 1) {
945 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
946 if ($act_status eq "down" && $new_status eq "down") {
947 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
948 $res = $known_server_db->del_dbentry($sql_statement);
949 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
950 }
951 else {
952 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
953 $res = $known_server_db->update_dbentry($sql_statement);
954 if($new_status eq "down"){
955 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
956 } else {
957 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
958 }
959 }
960 }
961 return $error;
962 }
965 sub update_jobdb_status_for_send_msgs {
966 my ($session_id, $answer, $error) = @_;
967 &daemon_log("$session_id DEBUG: try to update job status", 7);
968 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
969 my $jobdb_id = $1;
971 $answer =~ /<header>(.*)<\/header>/;
972 my $job_header = $1;
974 $answer =~ /<target>(.*)<\/target>/;
975 my $job_target = $1;
977 # Sending msg failed
978 if( $error ) {
980 # Set jobs to done, jobs do not need to deliver their message in any case
981 if (($job_header eq "trigger_action_localboot")
982 ||($job_header eq "trigger_action_lock")
983 ||($job_header eq "trigger_action_halt")
984 ) {
985 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
986 &daemon_log("$session_id DEBUG: $sql_statement", 7);
987 my $res = $job_db->update_dbentry($sql_statement);
989 # Reactivate jobs, jobs need to deliver their message
990 } elsif (($job_header eq "trigger_action_activate")
991 ||($job_header eq "trigger_action_update")
992 ||($job_header eq "trigger_action_reinstall")
993 ||($job_header eq "trigger_activate_new")
994 ) {
995 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
997 # For all other messages
998 } else {
999 my $sql_statement = "UPDATE $job_queue_tn ".
1000 "SET status='error', result='can not deliver msg, please consult log file' ".
1001 "WHERE id=$jobdb_id";
1002 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1003 my $res = $job_db->update_dbentry($sql_statement);
1004 }
1006 # Sending msg was successful
1007 } else {
1008 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1009 # jobs reinstall, update, inst_update do themself setting to done
1010 if (($job_header eq "trigger_action_localboot")
1011 ||($job_header eq "trigger_action_lock")
1012 ||($job_header eq "trigger_action_activate")
1013 ||($job_header eq "trigger_action_halt")
1014 ||($job_header eq "trigger_action_reboot")
1015 ||($job_header eq "trigger_action_wake")
1016 ||($job_header eq "trigger_wake")
1017 ) {
1019 my $sql_statement = "UPDATE $job_queue_tn ".
1020 "SET status='done' ".
1021 "WHERE id=$jobdb_id AND status='processed'";
1022 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1023 my $res = $job_db->update_dbentry($sql_statement);
1024 } else {
1025 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7);
1026 }
1027 }
1028 } else {
1029 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7);
1030 }
1031 }
1033 sub reactivate_job_with_delay {
1034 my ($session_id, $target, $header, $delay) = @_ ;
1035 # 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
1037 if (not defined $delay) { $delay = 30 } ;
1038 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1040 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE 'target' AND headertag='$header')";
1041 my $res = $job_db->update_dbentry($sql);
1042 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1043 "cause client '$target' is currently not available", 5);
1044 daemon_log("$session_id $sql", 7);
1045 return;
1046 }
1049 sub sig_handler {
1050 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1051 daemon_log("0 INFO got signal '$signal'", 1);
1052 $kernel->sig_handled();
1053 return;
1054 }
1057 sub msg_to_decrypt {
1058 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1059 my $session_id = $session->ID;
1060 my ($msg, $msg_hash, $module);
1061 my $error = 0;
1063 # fetch new msg out of @msgs_to_decrypt
1064 my $tmp_next_msg = shift @msgs_to_decrypt;
1065 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1067 # msg is from a new client or gosa
1068 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1070 # msg is from a gosa-si-server
1071 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1072 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1073 }
1074 # msg is from a gosa-si-client
1075 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1076 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1077 }
1078 # an error occurred
1079 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1080 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1081 # could not understand a msg from its server the client cause a re-registering process
1082 my $remote_ip = $heap->{'remote_ip'};
1083 my $remote_port = $heap->{'remote_port'};
1084 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1085 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1087 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1088 "' to cause a re-registering of the client if necessary", 3);
1089 $error++;
1090 }
1093 my $header;
1094 my $target;
1095 my $source;
1096 my $done = 0;
1097 my $sql;
1098 my $res;
1100 # check whether this message should be processed here
1101 if ($error == 0) {
1102 $header = @{$msg_hash->{'header'}}[0];
1103 $target = @{$msg_hash->{'target'}}[0];
1104 $source = @{$msg_hash->{'source'}}[0];
1105 my $not_found_in_known_clients_db = 0;
1106 my $not_found_in_known_server_db = 0;
1107 my $not_found_in_foreign_clients_db = 0;
1108 my $local_address;
1109 my $local_mac;
1110 my ($target_ip, $target_port) = split(':', $target);
1112 # Determine the local ip address if target is an ip address
1113 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1114 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1115 } else {
1116 $local_address = $server_address;
1117 }
1119 # Determine the local mac address if target is a mac address
1120 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) {
1121 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1122 my $network_interface= &get_interface_for_ip($loc_ip);
1123 $local_mac = &get_mac_for_interface($network_interface);
1124 } else {
1125 $local_mac = $server_mac_address;
1126 }
1128 # target and source is equal to GOSA -> process here
1129 if (not $done) {
1130 if ($target eq "GOSA" && $source eq "GOSA") {
1131 $done = 1;
1132 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1133 }
1134 }
1136 # target is own address without forward_to_gosa-tag -> process here
1137 if (not $done) {
1138 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1139 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1140 $done = 1;
1141 if ($source eq "GOSA") {
1142 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1143 }
1144 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1145 }
1146 }
1148 # target is a client address in known_clients -> process here
1149 if (not $done) {
1150 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1151 $res = $known_clients_db->select_dbentry($sql);
1152 if (keys(%$res) > 0) {
1153 $done = 1;
1154 my $hostname = $res->{1}->{'hostname'};
1155 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1156 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1157 if ($source eq "GOSA") {
1158 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1159 }
1160 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1162 } else {
1163 $not_found_in_known_clients_db = 1;
1164 }
1165 }
1167 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1168 if (not $done) {
1169 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1170 my $gosa_at;
1171 my $gosa_session_id;
1172 if (($target eq $local_address) && (defined $forward_to_gosa)){
1173 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1174 if ($gosa_at ne $local_address) {
1175 $done = 1;
1176 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1177 }
1178 }
1179 }
1181 # if message should be processed here -> add message to incoming_db
1182 if ($done) {
1183 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1184 # so gosa-si-server knows how to process this kind of messages
1185 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1186 $module = "GosaPackages";
1187 }
1189 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1190 primkey=>[],
1191 headertag=>$header,
1192 targettag=>$target,
1193 xmlmessage=>&encode_base64($msg),
1194 timestamp=>&get_time,
1195 module=>$module,
1196 sessionid=>$session_id,
1197 } );
1199 }
1201 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1202 if (not $done) {
1203 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1204 my $gosa_at;
1205 my $gosa_session_id;
1206 if (($target eq $local_address) && (defined $forward_to_gosa)){
1207 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1208 if ($gosa_at eq $local_address) {
1209 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1210 if( defined $session_reference ) {
1211 $heap = $session_reference->get_heap();
1212 }
1213 if(exists $heap->{'client'}) {
1214 $msg = &encrypt_msg($msg, $GosaPackages_key);
1215 $heap->{'client'}->put($msg);
1216 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1217 }
1218 $done = 1;
1219 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1220 }
1221 }
1223 }
1225 # target is a client address in foreign_clients -> forward to registration server
1226 if (not $done) {
1227 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1228 $res = $foreign_clients_db->select_dbentry($sql);
1229 if (keys(%$res) > 0) {
1230 my $hostname = $res->{1}->{'hostname'};
1231 my ($host_ip, $host_port) = split(/:/, $hostname);
1232 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1233 my $regserver = $res->{1}->{'regserver'};
1234 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1235 my $res = $known_server_db->select_dbentry($sql);
1236 if (keys(%$res) > 0) {
1237 my $regserver_key = $res->{1}->{'hostkey'};
1238 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1239 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1240 if ($source eq "GOSA") {
1241 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1242 }
1243 my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1244 if ($error) {
1245 &daemon_log("$session_id ERROR: some problems (error=$error) occurred while trying to send msg to registration server: $msg", 1);
1246 }
1247 }
1248 $done = 1;
1249 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1250 } else {
1251 $not_found_in_foreign_clients_db = 1;
1252 }
1253 }
1255 # target is a server address -> forward to server
1256 if (not $done) {
1257 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1258 $res = $known_server_db->select_dbentry($sql);
1259 if (keys(%$res) > 0) {
1260 my $hostkey = $res->{1}->{'hostkey'};
1262 if ($source eq "GOSA") {
1263 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1264 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1266 }
1268 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1269 $done = 1;
1270 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1271 } else {
1272 $not_found_in_known_server_db = 1;
1273 }
1274 }
1277 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1278 if ( $not_found_in_foreign_clients_db
1279 && $not_found_in_known_server_db
1280 && $not_found_in_known_clients_db) {
1281 &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);
1282 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1283 $module = "GosaPackages";
1284 }
1285 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1286 primkey=>[],
1287 headertag=>$header,
1288 targettag=>$target,
1289 xmlmessage=>&encode_base64($msg),
1290 timestamp=>&get_time,
1291 module=>$module,
1292 sessionid=>$session_id,
1293 } );
1294 $done = 1;
1295 }
1298 if (not $done) {
1299 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1300 if ($source eq "GOSA") {
1301 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1302 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1304 my $session_reference = $kernel->ID_id_to_session($session_id);
1305 if( defined $session_reference ) {
1306 $heap = $session_reference->get_heap();
1307 }
1308 if(exists $heap->{'client'}) {
1309 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1310 $heap->{'client'}->put($error_msg);
1311 }
1312 }
1313 }
1315 }
1317 return;
1318 }
1321 sub next_task {
1322 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1323 my $running_task = POE::Wheel::Run->new(
1324 Program => sub { process_task($session, $heap, $task) },
1325 StdioFilter => POE::Filter::Reference->new(),
1326 StdoutEvent => "task_result",
1327 StderrEvent => "task_debug",
1328 CloseEvent => "task_done",
1329 );
1330 $heap->{task}->{ $running_task->ID } = $running_task;
1331 }
1333 sub handle_task_result {
1334 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1335 my $client_answer = $result->{'answer'};
1336 if( $client_answer =~ s/session_id=(\d+)$// ) {
1337 my $session_id = $1;
1338 if( defined $session_id ) {
1339 my $session_reference = $kernel->ID_id_to_session($session_id);
1340 if( defined $session_reference ) {
1341 $heap = $session_reference->get_heap();
1342 }
1343 }
1345 if(exists $heap->{'client'}) {
1346 $heap->{'client'}->put($client_answer);
1347 }
1348 }
1349 $kernel->sig(CHLD => "child_reap");
1350 }
1352 sub handle_task_debug {
1353 my $result = $_[ARG0];
1354 print STDERR "$result\n";
1355 }
1357 sub handle_task_done {
1358 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1359 delete $heap->{task}->{$task_id};
1360 if (exists $heap->{ldap_handle}->{$task_id}) {
1361 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1362 }
1363 }
1365 sub process_task {
1366 no strict "refs";
1367 #CHECK: Not @_[...]?
1368 my ($session, $heap, $task) = @_;
1369 my $error = 0;
1370 my $answer_l;
1371 my ($answer_header, @answer_target_l, $answer_source);
1372 my $client_answer = "";
1374 # prepare all variables needed to process message
1375 #my $msg = $task->{'xmlmessage'};
1376 my $msg = &decode_base64($task->{'xmlmessage'});
1377 my $incoming_id = $task->{'id'};
1378 my $module = $task->{'module'};
1379 my $header = $task->{'headertag'};
1380 my $session_id = $task->{'sessionid'};
1381 my $msg_hash;
1382 eval {
1383 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1384 };
1385 daemon_log("ERROR: XML failure '$@'") if ($@);
1386 my $source = @{$msg_hash->{'source'}}[0];
1388 # set timestamp of incoming client uptodate, so client will not
1389 # be deleted from known_clients because of expiration
1390 my $cur_time = &get_time();
1391 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1392 my $res = $known_clients_db->exec_statement($sql);
1394 ######################
1395 # process incoming msg
1396 if( $error == 0) {
1397 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1398 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1399 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1401 if ( 0 < @{$answer_l} ) {
1402 my $answer_str = join("\n", @{$answer_l});
1403 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1404 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1405 }
1406 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1407 } else {
1408 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1409 }
1411 }
1412 if( !$answer_l ) { $error++ };
1414 ########
1415 # answer
1416 if( $error == 0 ) {
1418 foreach my $answer ( @{$answer_l} ) {
1419 # check outgoing msg to xml validity
1420 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1421 if( not defined $answer_hash ) { next; }
1423 $answer_header = @{$answer_hash->{'header'}}[0];
1424 @answer_target_l = @{$answer_hash->{'target'}};
1425 $answer_source = @{$answer_hash->{'source'}}[0];
1427 # deliver msg to all targets
1428 foreach my $answer_target ( @answer_target_l ) {
1430 # targets of msg are all gosa-si-clients in known_clients_db
1431 if( $answer_target eq "*" ) {
1432 # answer is for all clients
1433 my $sql_statement= "SELECT * FROM known_clients";
1434 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1435 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1436 my $host_name = $hit->{hostname};
1437 my $host_key = $hit->{hostkey};
1438 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1439 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1440 }
1441 }
1443 # targets of msg are all gosa-si-server in known_server_db
1444 elsif( $answer_target eq "KNOWN_SERVER" ) {
1445 # answer is for all server in known_server
1446 my $sql_statement= "SELECT * FROM $known_server_tn";
1447 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1448 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1449 my $host_name = $hit->{hostname};
1450 my $host_key = $hit->{hostkey};
1451 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1452 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1453 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1454 }
1455 }
1457 # target of msg is GOsa
1458 elsif( $answer_target eq "GOSA" ) {
1459 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1460 my $add_on = "";
1461 if( defined $session_id ) {
1462 $add_on = ".session_id=$session_id";
1463 }
1464 # answer is for GOSA and has to returned to connected client
1465 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1466 $client_answer = $gosa_answer.$add_on;
1467 }
1469 # target of msg is job queue at this host
1470 elsif( $answer_target eq "JOBDB") {
1471 $answer =~ /<header>(\S+)<\/header>/;
1472 my $header;
1473 if( defined $1 ) { $header = $1; }
1474 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1475 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1476 }
1478 # Target of msg is a mac address
1479 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 ) {
1480 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1482 # Looking for macaddress in known_clients
1483 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1484 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1485 my $found_ip_flag = 0;
1486 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1487 my $host_name = $hit->{hostname};
1488 my $host_key = $hit->{hostkey};
1489 $answer =~ s/$answer_target/$host_name/g;
1490 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1491 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1492 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1493 $found_ip_flag++ ;
1494 }
1496 # Looking for macaddress in foreign_clients
1497 if ($found_ip_flag == 0) {
1498 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1499 my $res = $foreign_clients_db->select_dbentry($sql);
1500 while( my ($hit_num, $hit) = each %{ $res } ) {
1501 my $host_name = $hit->{hostname};
1502 my $reg_server = $hit->{regserver};
1503 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1505 # Fetch key for reg_server
1506 my $reg_server_key;
1507 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1508 my $res = $known_server_db->select_dbentry($sql);
1509 if (exists $res->{1}) {
1510 $reg_server_key = $res->{1}->{'hostkey'};
1511 } else {
1512 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1513 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1514 $reg_server_key = undef;
1515 }
1517 # Send answer to server where client is registered
1518 if (defined $reg_server_key) {
1519 $answer =~ s/$answer_target/$host_name/g;
1520 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1521 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1522 $found_ip_flag++ ;
1523 }
1524 }
1525 }
1527 # No mac to ip matching found
1528 if( $found_ip_flag == 0) {
1529 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1530 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1531 }
1533 # Answer is for one specific host
1534 } else {
1535 # get encrypt_key
1536 my $encrypt_key = &get_encrypt_key($answer_target);
1537 if( not defined $encrypt_key ) {
1538 # unknown target
1539 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1540 next;
1541 }
1542 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1543 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1544 }
1545 }
1546 }
1547 }
1549 my $filter = POE::Filter::Reference->new();
1550 my %result = (
1551 status => "seems ok to me",
1552 answer => $client_answer,
1553 );
1555 my $output = $filter->put( [ \%result ] );
1556 print @$output;
1559 }
1561 sub session_start {
1562 my ($kernel) = $_[KERNEL];
1563 $global_kernel = $kernel;
1564 $kernel->yield('register_at_foreign_servers');
1565 $kernel->yield('create_fai_server_db', $fai_server_tn );
1566 $kernel->yield('create_fai_release_db', $fai_release_tn );
1567 $kernel->yield('watch_for_next_tasks');
1568 $kernel->sig(USR1 => "sig_handler");
1569 $kernel->sig(USR2 => "recreate_packages_db");
1570 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1571 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1572 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1573 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1574 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1575 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1576 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1578 # Start opsi check
1579 if ($opsi_enabled eq "true") {
1580 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1581 }
1583 }
1586 sub watch_for_done_jobs {
1587 #CHECK: $heap for what?
1588 my ($kernel,$heap) = @_[KERNEL, HEAP];
1590 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1591 my $res = $job_db->select_dbentry( $sql_statement );
1593 while( my ($id, $hit) = each %{$res} ) {
1594 my $jobdb_id = $hit->{id};
1595 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1596 my $res = $job_db->del_dbentry($sql_statement);
1597 }
1599 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1600 }
1603 sub watch_for_opsi_jobs {
1604 my ($kernel) = $_[KERNEL];
1606 # 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
1607 # opsi install job is to parse the xml message. There is still the correct header.
1608 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1609 my $res = $job_db->select_dbentry( $sql_statement );
1611 # Ask OPSI for an update of the running jobs
1612 while (my ($id, $hit) = each %$res ) {
1613 # Determine current parameters of the job
1614 my $hostId = $hit->{'plainname'};
1615 my $macaddress = $hit->{'macaddress'};
1616 my $progress = $hit->{'progress'};
1618 my $result= {};
1620 # For hosts, only return the products that are or get installed
1621 my $callobj;
1622 $callobj = {
1623 method => 'getProductStates_hash',
1624 params => [ $hostId ],
1625 id => 1,
1626 };
1628 my $hres = $opsi_client->call($opsi_url, $callobj);
1629 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1630 if (not &check_opsi_res($hres)) {
1631 my $htmp= $hres->result->{$hostId};
1633 # Check state != not_installed or action == setup -> load and add
1634 my $products= 0;
1635 my $installed= 0;
1636 my $installing = 0;
1637 my $error= 0;
1638 my @installed_list;
1639 my @error_list;
1640 my $act_status = "none";
1641 foreach my $product (@{$htmp}){
1643 if ($product->{'installationStatus'} ne "not_installed" or
1644 $product->{'actionRequest'} eq "setup"){
1646 # Increase number of products for this host
1647 $products++;
1649 if ($product->{'installationStatus'} eq "failed"){
1650 $result->{$product->{'productId'}}= "error";
1651 unshift(@error_list, $product->{'productId'});
1652 $error++;
1653 }
1654 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1655 $result->{$product->{'productId'}}= "installed";
1656 unshift(@installed_list, $product->{'productId'});
1657 $installed++;
1658 }
1659 if ($product->{'installationStatus'} eq "installing"){
1660 $result->{$product->{'productId'}}= "installing";
1661 $installing++;
1662 $act_status = "installing - ".$product->{'productId'};
1663 }
1664 }
1665 }
1667 # Estimate "rough" progress, avoid division by zero
1668 if ($products == 0) {
1669 $result->{'progress'}= 0;
1670 } else {
1671 $result->{'progress'}= int($installed * 100 / $products);
1672 }
1674 # Set updates in job queue
1675 if ((not $error) && (not $installing) && ($installed)) {
1676 $act_status = "installed - ".join(", ", @installed_list);
1677 }
1678 if ($error) {
1679 $act_status = "error - ".join(", ", @error_list);
1680 }
1681 if ($progress ne $result->{'progress'} ) {
1682 # Updating progress and result
1683 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1684 my $update_res = $job_db->update_dbentry($update_statement);
1685 }
1686 if ($progress eq 100) {
1687 # Updateing status
1688 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1689 if ($error) {
1690 $done_statement .= "status='error'";
1691 } else {
1692 $done_statement .= "status='done'";
1693 }
1694 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1695 my $done_res = $job_db->update_dbentry($done_statement);
1696 }
1699 }
1700 }
1702 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1703 }
1706 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1707 sub watch_for_modified_jobs {
1708 my ($kernel,$heap) = @_[KERNEL, HEAP];
1710 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1711 my $res = $job_db->select_dbentry( $sql_statement );
1713 # if db contains no jobs which should be update, do nothing
1714 if (keys %$res != 0) {
1716 if ($job_synchronization eq "true") {
1717 # make out of the db result a gosa-si message
1718 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1720 # update all other SI-server
1721 &inform_all_other_si_server($update_msg);
1722 }
1724 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1725 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1726 $res = $job_db->update_dbentry($sql_statement);
1727 }
1729 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1730 }
1733 sub watch_for_new_jobs {
1734 if($watch_for_new_jobs_in_progress == 0) {
1735 $watch_for_new_jobs_in_progress = 1;
1736 my ($kernel,$heap) = @_[KERNEL, HEAP];
1738 # check gosa job queue for jobs with executable timestamp
1739 my $timestamp = &get_time();
1740 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1741 my $res = $job_db->exec_statement( $sql_statement );
1743 # Merge all new jobs that would do the same actions
1744 my @drops;
1745 my $hits;
1746 foreach my $hit (reverse @{$res} ) {
1747 my $macaddress= lc @{$hit}[8];
1748 my $headertag= @{$hit}[5];
1749 if(
1750 defined($hits->{$macaddress}) &&
1751 defined($hits->{$macaddress}->{$headertag}) &&
1752 defined($hits->{$macaddress}->{$headertag}[0])
1753 ) {
1754 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1755 }
1756 $hits->{$macaddress}->{$headertag}= $hit;
1757 }
1759 # Delete new jobs with a matching job in state 'processing'
1760 foreach my $macaddress (keys %{$hits}) {
1761 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1762 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1763 if(defined($jobdb_id)) {
1764 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1765 my $res = $job_db->exec_statement( $sql_statement );
1766 foreach my $hit (@{$res}) {
1767 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1768 }
1769 } else {
1770 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1771 }
1772 }
1773 }
1775 # Commit deletion
1776 $job_db->exec_statementlist(\@drops);
1778 # Look for new jobs that could be executed
1779 foreach my $macaddress (keys %{$hits}) {
1781 # Look if there is an executing job
1782 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1783 my $res = $job_db->exec_statement( $sql_statement );
1785 # Skip new jobs for host if there is a processing job
1786 if(defined($res) and defined @{$res}[0]) {
1787 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1788 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1789 if(@{$row}[5] eq 'trigger_action_reinstall') {
1790 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1791 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1792 if(defined($res_2) and defined @{$res_2}[0]) {
1793 # Set status from goto-activation to 'waiting' and update timestamp
1794 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1795 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1796 }
1797 }
1798 next;
1799 }
1801 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1802 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1803 if(defined($jobdb_id)) {
1804 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1806 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1807 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1808 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1810 # expect macaddress is unique!!!!!!
1811 my $target = $res_hash->{1}->{hostname};
1813 # change header
1814 $job_msg =~ s/<header>job_/<header>gosa_/;
1816 # add sqlite_id
1817 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1819 $job_msg =~ /<header>(\S+)<\/header>/;
1820 my $header = $1 ;
1821 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1823 # update status in job queue to ...
1824 # ... 'processing', for jobs: 'reinstall', 'update'
1825 if (($header =~ /gosa_trigger_action_reinstall/)
1826 || ($header =~ /gosa_trigger_activate_new/)
1827 || ($header =~ /gosa_trigger_action_update/)) {
1828 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1829 my $dbres = $job_db->update_dbentry($sql_statement);
1830 }
1832 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1833 else {
1834 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1835 my $dbres = $job_db->update_dbentry($sql_statement);
1836 }
1839 # We don't want parallel processing
1840 last;
1841 }
1842 }
1843 }
1845 $watch_for_new_jobs_in_progress = 0;
1846 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1847 }
1848 }
1851 sub watch_for_new_messages {
1852 my ($kernel,$heap) = @_[KERNEL, HEAP];
1853 my @coll_user_msg; # collection list of outgoing messages
1855 # check messaging_db for new incoming messages with executable timestamp
1856 my $timestamp = &get_time();
1857 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1858 my $res = $messaging_db->exec_statement( $sql_statement );
1859 foreach my $hit (@{$res}) {
1861 # create outgoing messages
1862 my $message_to = @{$hit}[3];
1863 # translate message_to to plain login name
1864 my @message_to_l = split(/,/, $message_to);
1865 my %receiver_h;
1866 foreach my $receiver (@message_to_l) {
1867 if ($receiver =~ /^u_([\s\S]*)$/) {
1868 $receiver_h{$1} = 0;
1869 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1870 my $group_name = $1;
1871 # fetch all group members from ldap and add them to receiver hash
1872 my $ldap_handle = &get_ldap_handle();
1873 if (defined $ldap_handle) {
1874 my $mesg = $ldap_handle->search(
1875 base => $ldap_base,
1876 scope => 'sub',
1877 attrs => ['memberUid'],
1878 filter => "cn=$group_name",
1879 );
1880 if ($mesg->count) {
1881 my @entries = $mesg->entries;
1882 foreach my $entry (@entries) {
1883 my @receivers= $entry->get_value("memberUid");
1884 foreach my $receiver (@receivers) {
1885 $receiver_h{$receiver} = 0;
1886 }
1887 }
1888 }
1889 # translating errors ?
1890 if ($mesg->code) {
1891 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1892 }
1893 &release_ldap_handle($ldap_handle);
1894 # ldap handle error ?
1895 } else {
1896 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1897 }
1898 } else {
1899 my $sbjct = &encode_base64(@{$hit}[1]);
1900 my $msg = &encode_base64(@{$hit}[7]);
1901 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1902 }
1903 }
1904 my @receiver_l = keys(%receiver_h);
1906 my $message_id = @{$hit}[0];
1908 #add each outgoing msg to messaging_db
1909 my $receiver;
1910 foreach $receiver (@receiver_l) {
1911 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1912 "VALUES ('".
1913 $message_id."', '". # id
1914 @{$hit}[1]."', '". # subject
1915 @{$hit}[2]."', '". # message_from
1916 $receiver."', '". # message_to
1917 "none"."', '". # flag
1918 "out"."', '". # direction
1919 @{$hit}[6]."', '". # delivery_time
1920 @{$hit}[7]."', '". # message
1921 $timestamp."'". # timestamp
1922 ")";
1923 &daemon_log("M DEBUG: $sql_statement", 1);
1924 my $res = $messaging_db->exec_statement($sql_statement);
1925 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1926 }
1928 # set incoming message to flag d=deliverd
1929 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1930 &daemon_log("M DEBUG: $sql_statement", 7);
1931 $res = $messaging_db->update_dbentry($sql_statement);
1932 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1933 }
1935 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1936 return;
1937 }
1939 sub watch_for_delivery_messages {
1940 my ($kernel, $heap) = @_[KERNEL, HEAP];
1942 # select outgoing messages
1943 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1944 #&daemon_log("0 DEBUG: $sql", 7);
1945 my $res = $messaging_db->exec_statement( $sql_statement );
1947 # build out msg for each usr
1948 foreach my $hit (@{$res}) {
1949 my $receiver = @{$hit}[3];
1950 my $msg_id = @{$hit}[0];
1951 my $subject = @{$hit}[1];
1952 my $message = @{$hit}[7];
1954 # resolve usr -> host where usr is logged in
1955 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1956 #&daemon_log("0 DEBUG: $sql", 7);
1957 my $res = $login_users_db->exec_statement($sql);
1959 # receiver is logged in nowhere
1960 if (not ref(@$res[0]) eq "ARRAY") { next; }
1962 # receiver ist logged in at a client registered at local server
1963 my $send_succeed = 0;
1964 foreach my $hit (@$res) {
1965 my $receiver_host = @$hit[0];
1966 my $delivered2host = 0;
1967 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1969 # Looking for host in know_clients_db
1970 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1971 my $res = $known_clients_db->exec_statement($sql);
1973 # Host is known in known_clients_db
1974 if (ref(@$res[0]) eq "ARRAY") {
1975 my $receiver_key = @{@{$res}[0]}[2];
1976 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1977 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1978 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1979 if ($error == 0 ) {
1980 $send_succeed++ ;
1981 $delivered2host++ ;
1982 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1983 } else {
1984 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1985 }
1986 }
1988 # Message already send, do not need to do anything more, otherwise ...
1989 if ($delivered2host) { next;}
1991 # ...looking for host in foreign_clients_db
1992 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1993 $res = $foreign_clients_db->exec_statement($sql);
1995 # Host is known in foreign_clients_db
1996 if (ref(@$res[0]) eq "ARRAY") {
1997 my $registration_server = @{@{$res}[0]}[2];
1999 # Fetch encryption key for registration server
2000 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2001 my $res = $known_server_db->exec_statement($sql);
2002 if (ref(@$res[0]) eq "ARRAY") {
2003 my $registration_server_key = @{@{$res}[0]}[3];
2004 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2005 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2006 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2007 if ($error == 0 ) {
2008 $send_succeed++ ;
2009 $delivered2host++ ;
2010 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2011 } else {
2012 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2013 }
2015 } else {
2016 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2017 "registrated at server '$registration_server', ".
2018 "but no data available in known_server_db ", 1);
2019 }
2020 }
2022 if (not $delivered2host) {
2023 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2024 }
2025 }
2027 if ($send_succeed) {
2028 # set outgoing msg at db to deliverd
2029 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2030 my $res = $messaging_db->exec_statement($sql);
2031 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2032 } else {
2033 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2034 }
2035 }
2037 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2038 return;
2039 }
2042 sub watch_for_done_messages {
2043 my ($kernel,$heap) = @_[KERNEL, HEAP];
2045 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2046 #&daemon_log("0 DEBUG: $sql", 7);
2047 my $res = $messaging_db->exec_statement($sql);
2049 foreach my $hit (@{$res}) {
2050 my $msg_id = @{$hit}[0];
2052 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2053 #&daemon_log("0 DEBUG: $sql", 7);
2054 my $res = $messaging_db->exec_statement($sql);
2056 # not all usr msgs have been seen till now
2057 if ( ref(@$res[0]) eq "ARRAY") { next; }
2059 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2060 #&daemon_log("0 DEBUG: $sql", 7);
2061 $res = $messaging_db->exec_statement($sql);
2063 }
2065 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2066 return;
2067 }
2070 sub watch_for_old_known_clients {
2071 my ($kernel,$heap) = @_[KERNEL, HEAP];
2073 my $sql_statement = "SELECT * FROM $known_clients_tn";
2074 my $res = $known_clients_db->select_dbentry( $sql_statement );
2076 my $cur_time = int(&get_time());
2078 while ( my ($hit_num, $hit) = each %$res) {
2079 my $expired_timestamp = int($hit->{'timestamp'});
2080 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2081 my $dt = DateTime->new( year => $1,
2082 month => $2,
2083 day => $3,
2084 hour => $4,
2085 minute => $5,
2086 second => $6,
2087 );
2089 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2090 $expired_timestamp = $dt->ymd('').$dt->hms('');
2091 if ($cur_time > $expired_timestamp) {
2092 my $hostname = $hit->{'hostname'};
2093 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2094 my $del_res = $known_clients_db->exec_statement($del_sql);
2096 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2097 }
2099 }
2101 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2102 }
2105 sub watch_for_next_tasks {
2106 my ($kernel,$heap) = @_[KERNEL, HEAP];
2108 my $sql = "SELECT * FROM $incoming_tn";
2109 my $res = $incoming_db->select_dbentry($sql);
2111 while ( my ($hit_num, $hit) = each %$res) {
2112 my $headertag = $hit->{'headertag'};
2113 if ($headertag =~ /^answer_(\d+)/) {
2114 # do not start processing, this message is for a still running POE::Wheel
2115 next;
2116 }
2117 my $message_id = $hit->{'id'};
2118 my $session_id = $hit->{'sessionid'};
2119 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2121 $kernel->yield('next_task', $hit);
2123 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2124 my $res = $incoming_db->exec_statement($sql);
2125 }
2127 $kernel->delay_set('watch_for_next_tasks', 1);
2128 }
2131 sub get_ldap_handle {
2132 my ($session_id) = @_;
2133 my $heap;
2135 if (not defined $session_id ) { $session_id = 0 };
2136 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2138 my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2139 my $caller_text = "subroutine $subroutine";
2140 if ($subroutine eq "(eval)") {
2141 $caller_text = "eval block within file '$file' for '$evalText'";
2142 }
2143 daemon_log("$session_id INFO: new ldap handle for '$caller_text' required!", 1);
2145 get_handle:
2146 my $ldap_handle = Net::LDAP->new( $ldap_uri );
2147 if (not ref $ldap_handle) {
2148 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying!", 1);
2149 usleep(1000);
2150 goto get_handle;
2151 } else {
2152 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 6);
2153 }
2155 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or &daemon_log("$session_id ERROR: Could not bind as '$ldap_admin_dn' to LDAP URI '$ldap_uri'!", 1);
2156 return $ldap_handle;
2157 }
2160 sub release_ldap_handle {
2161 my ($ldap_handle) = @_ ;
2162 if(ref $ldap_handle) {
2163 $ldap_handle->disconnect();
2164 }
2165 &main::daemon_log("0 DEBUG: Released a ldap handle!", 6);
2166 return;
2167 }
2170 sub change_fai_state {
2171 my ($st, $targets, $session_id) = @_;
2172 $session_id = 0 if not defined $session_id;
2173 # Set FAI state to localboot
2174 my %mapActions= (
2175 reboot => '',
2176 update => 'softupdate',
2177 localboot => 'localboot',
2178 reinstall => 'install',
2179 rescan => '',
2180 wake => '',
2181 memcheck => 'memcheck',
2182 sysinfo => 'sysinfo',
2183 install => 'install',
2184 );
2186 # Return if this is unknown
2187 if (!exists $mapActions{ $st }){
2188 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2189 return;
2190 }
2192 my $state= $mapActions{ $st };
2194 # Build search filter for hosts
2195 my $search= "(&(objectClass=GOhard)";
2196 foreach (@{$targets}){
2197 $search.= "(macAddress=$_)";
2198 }
2199 $search.= ")";
2201 # If there's any host inside of the search string, procress them
2202 if (!($search =~ /macAddress/)){
2203 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2204 return;
2205 }
2207 my $ldap_handle = &get_ldap_handle($session_id);
2208 # Perform search for Unit Tag
2209 my $mesg = $ldap_handle->search(
2210 base => $ldap_base,
2211 scope => 'sub',
2212 attrs => ['dn', 'FAIstate', 'objectClass'],
2213 filter => "$search"
2214 );
2216 if ($mesg->count) {
2217 my @entries = $mesg->entries;
2218 if (0 == @entries) {
2219 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2220 }
2222 foreach my $entry (@entries) {
2223 # Only modify entry if it is not set to '$state'
2224 if ($entry->get_value("FAIstate") ne "$state"){
2225 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2226 my $result;
2227 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2228 if (exists $tmp{'FAIobject'}){
2229 if ($state eq ''){
2230 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2231 } else {
2232 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2233 }
2234 } elsif ($state ne ''){
2235 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2236 }
2238 # Errors?
2239 if ($result->code){
2240 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2241 }
2242 } else {
2243 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2244 }
2245 }
2246 } else {
2247 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2248 }
2249 &release_ldap_handle($ldap_handle);
2251 return;
2252 }
2255 sub change_goto_state {
2256 my ($st, $targets, $session_id) = @_;
2257 $session_id = 0 if not defined $session_id;
2259 # Switch on or off?
2260 my $state= $st eq 'active' ? 'active': 'locked';
2262 my $ldap_handle = &get_ldap_handle($session_id);
2263 if( defined($ldap_handle) ) {
2265 # Build search filter for hosts
2266 my $search= "(&(objectClass=GOhard)";
2267 foreach (@{$targets}){
2268 $search.= "(macAddress=$_)";
2269 }
2270 $search.= ")";
2272 # If there's any host inside of the search string, procress them
2273 if (!($search =~ /macAddress/)){
2274 &release_ldap_handle($ldap_handle);
2275 return;
2276 }
2278 # Perform search for Unit Tag
2279 my $mesg = $ldap_handle->search(
2280 base => $ldap_base,
2281 scope => 'sub',
2282 attrs => ['dn', 'gotoMode'],
2283 filter => "$search"
2284 );
2286 if ($mesg->count) {
2287 my @entries = $mesg->entries;
2288 foreach my $entry (@entries) {
2290 # Only modify entry if it is not set to '$state'
2291 if ($entry->get_value("gotoMode") ne $state){
2293 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2294 my $result;
2295 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2297 # Errors?
2298 if ($result->code){
2299 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2300 }
2302 }
2303 }
2304 } else {
2305 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2306 }
2308 }
2309 &release_ldap_handle($ldap_handle);
2310 return;
2311 }
2314 sub run_recreate_packages_db {
2315 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2316 my $session_id = $session->ID;
2317 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2318 $kernel->yield('create_fai_release_db', $fai_release_tn);
2319 $kernel->yield('create_fai_server_db', $fai_server_tn);
2320 return;
2321 }
2324 sub run_create_fai_server_db {
2325 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2326 my $session_id = $session->ID;
2327 my $task = POE::Wheel::Run->new(
2328 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2329 StdoutEvent => "session_run_result",
2330 StderrEvent => "session_run_debug",
2331 CloseEvent => "session_run_done",
2332 );
2334 $heap->{task}->{ $task->ID } = $task;
2335 return;
2336 }
2339 sub create_fai_server_db {
2340 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2341 my $result;
2343 if (not defined $session_id) { $session_id = 0; }
2344 my $ldap_handle = &get_ldap_handle($session_id);
2345 if(defined($ldap_handle)) {
2346 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2347 my $mesg= $ldap_handle->search(
2348 base => $ldap_base,
2349 scope => 'sub',
2350 attrs => ['FAIrepository', 'gosaUnitTag'],
2351 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2352 );
2353 if($mesg->{'resultCode'} == 0 &&
2354 $mesg->count != 0) {
2355 foreach my $entry (@{$mesg->{entries}}) {
2356 if($entry->exists('FAIrepository')) {
2357 # Add an entry for each Repository configured for server
2358 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2359 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2360 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2361 $result= $fai_server_db->add_dbentry( {
2362 table => $table_name,
2363 primkey => ['server', 'fai_release', 'tag'],
2364 server => $tmp_url,
2365 fai_release => $tmp_release,
2366 sections => $tmp_sections,
2367 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2368 } );
2369 }
2370 }
2371 }
2372 }
2373 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2374 &release_ldap_handle($ldap_handle);
2376 # TODO: Find a way to post the 'create_packages_list_db' event
2377 if(not defined($dont_create_packages_list)) {
2378 &create_packages_list_db(undef, $session_id);
2379 }
2380 }
2382 return $result;
2383 }
2386 sub run_create_fai_release_db {
2387 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2388 my $session_id = $session->ID;
2389 my $task = POE::Wheel::Run->new(
2390 Program => sub { &create_fai_release_db($table_name, $session_id) },
2391 StdoutEvent => "session_run_result",
2392 StderrEvent => "session_run_debug",
2393 CloseEvent => "session_run_done",
2394 );
2396 $heap->{task}->{ $task->ID } = $task;
2397 return;
2398 }
2401 sub create_fai_release_db {
2402 my ($table_name, $session_id) = @_;
2403 my $result;
2405 # used for logging
2406 if (not defined $session_id) { $session_id = 0; }
2408 my $ldap_handle = &get_ldap_handle($session_id);
2409 if(defined($ldap_handle)) {
2410 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2411 my $mesg= $ldap_handle->search(
2412 base => $ldap_base,
2413 scope => 'sub',
2414 attrs => [],
2415 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2416 );
2417 if(($mesg->code == 0) && ($mesg->count != 0))
2418 {
2419 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2421 # Walk through all possible FAI container ou's
2422 my @sql_list;
2423 my $timestamp= &get_time();
2424 foreach my $ou (@{$mesg->{entries}}) {
2425 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2426 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2427 my @tmp_array=get_fai_release_entries($tmp_classes);
2428 if(@tmp_array) {
2429 foreach my $entry (@tmp_array) {
2430 if(defined($entry) && ref($entry) eq 'HASH') {
2431 my $sql=
2432 "INSERT INTO $table_name "
2433 ."(timestamp, fai_release, class, type, state) VALUES ("
2434 .$timestamp.","
2435 ."'".$entry->{'release'}."',"
2436 ."'".$entry->{'class'}."',"
2437 ."'".$entry->{'type'}."',"
2438 ."'".$entry->{'state'}."')";
2439 push @sql_list, $sql;
2440 }
2441 }
2442 }
2443 }
2444 }
2446 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2447 &release_ldap_handle($ldap_handle);
2448 if(@sql_list) {
2449 unshift @sql_list, "VACUUM";
2450 unshift @sql_list, "DELETE FROM $table_name";
2451 $fai_release_db->exec_statementlist(\@sql_list);
2452 }
2453 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2454 } else {
2455 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2456 }
2457 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2458 }
2459 return $result;
2460 }
2462 sub get_fai_types {
2463 my $tmp_classes = shift || return undef;
2464 my @result;
2466 foreach my $type(keys %{$tmp_classes}) {
2467 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2468 my $entry = {
2469 type => $type,
2470 state => $tmp_classes->{$type}[0],
2471 };
2472 push @result, $entry;
2473 }
2474 }
2476 return @result;
2477 }
2479 sub get_fai_state {
2480 my $result = "";
2481 my $tmp_classes = shift || return $result;
2483 foreach my $type(keys %{$tmp_classes}) {
2484 if(defined($tmp_classes->{$type}[0])) {
2485 $result = $tmp_classes->{$type}[0];
2487 # State is equal for all types in class
2488 last;
2489 }
2490 }
2492 return $result;
2493 }
2495 sub resolve_fai_classes {
2496 my ($fai_base, $ldap_handle, $session_id) = @_;
2497 if (not defined $session_id) { $session_id = 0; }
2498 my $result;
2499 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2500 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2501 my $fai_classes;
2503 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2504 my $mesg= $ldap_handle->search(
2505 base => $fai_base,
2506 scope => 'sub',
2507 attrs => ['cn','objectClass','FAIstate'],
2508 filter => $fai_filter,
2509 );
2510 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2512 if($mesg->{'resultCode'} == 0 &&
2513 $mesg->count != 0) {
2514 foreach my $entry (@{$mesg->{entries}}) {
2515 if($entry->exists('cn')) {
2516 my $tmp_dn= $entry->dn();
2517 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2518 - length($fai_base) - 1 );
2520 # Skip classname and ou dn parts for class
2521 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2523 # Skip classes without releases
2524 if((!defined($tmp_release)) || length($tmp_release)==0) {
2525 next;
2526 }
2528 my $tmp_cn= $entry->get_value('cn');
2529 my $tmp_state= $entry->get_value('FAIstate');
2531 my $tmp_type;
2532 # Get FAI type
2533 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2534 if(grep $_ eq $oclass, @possible_fai_classes) {
2535 $tmp_type= $oclass;
2536 last;
2537 }
2538 }
2540 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2541 # A Subrelease
2542 my @sub_releases = split(/,/, $tmp_release);
2544 # Walk through subreleases and build hash tree
2545 my $hash;
2546 while(my $tmp_sub_release = pop @sub_releases) {
2547 $hash .= "\{'$tmp_sub_release'\}->";
2548 }
2549 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2550 } else {
2551 # A branch, no subrelease
2552 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2553 }
2554 } elsif (!$entry->exists('cn')) {
2555 my $tmp_dn= $entry->dn();
2556 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2557 - length($fai_base) - 1 );
2558 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2560 # Skip classes without releases
2561 if((!defined($tmp_release)) || length($tmp_release)==0) {
2562 next;
2563 }
2565 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2566 # A Subrelease
2567 my @sub_releases= split(/,/, $tmp_release);
2569 # Walk through subreleases and build hash tree
2570 my $hash;
2571 while(my $tmp_sub_release = pop @sub_releases) {
2572 $hash .= "\{'$tmp_sub_release'\}->";
2573 }
2574 # Remove the last two characters
2575 chop($hash);
2576 chop($hash);
2578 eval('$fai_classes->'.$hash.'= {}');
2579 } else {
2580 # A branch, no subrelease
2581 if(!exists($fai_classes->{$tmp_release})) {
2582 $fai_classes->{$tmp_release} = {};
2583 }
2584 }
2585 }
2586 }
2588 # The hash is complete, now we can honor the copy-on-write based missing entries
2589 foreach my $release (keys %$fai_classes) {
2590 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2591 }
2592 }
2593 return $result;
2594 }
2596 sub apply_fai_inheritance {
2597 my $fai_classes = shift || return {};
2598 my $tmp_classes;
2600 # Get the classes from the branch
2601 foreach my $class (keys %{$fai_classes}) {
2602 # Skip subreleases
2603 if($class =~ /^ou=.*$/) {
2604 next;
2605 } else {
2606 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2607 }
2608 }
2610 # Apply to each subrelease
2611 foreach my $subrelease (keys %{$fai_classes}) {
2612 if($subrelease =~ /ou=/) {
2613 foreach my $tmp_class (keys %{$tmp_classes}) {
2614 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2615 $fai_classes->{$subrelease}->{$tmp_class} =
2616 deep_copy($tmp_classes->{$tmp_class});
2617 } else {
2618 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2619 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2620 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2621 deep_copy($tmp_classes->{$tmp_class}->{$type});
2622 }
2623 }
2624 }
2625 }
2626 }
2627 }
2629 # Find subreleases in deeper levels
2630 foreach my $subrelease (keys %{$fai_classes}) {
2631 if($subrelease =~ /ou=/) {
2632 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2633 if($subsubrelease =~ /ou=/) {
2634 apply_fai_inheritance($fai_classes->{$subrelease});
2635 }
2636 }
2637 }
2638 }
2640 return $fai_classes;
2641 }
2643 sub get_fai_release_entries {
2644 my $tmp_classes = shift || return;
2645 my $parent = shift || "";
2646 my @result = shift || ();
2648 foreach my $entry (keys %{$tmp_classes}) {
2649 if(defined($entry)) {
2650 if($entry =~ /^ou=.*$/) {
2651 my $release_name = $entry;
2652 $release_name =~ s/ou=//g;
2653 if(length($parent)>0) {
2654 $release_name = $parent."/".$release_name;
2655 }
2656 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2657 foreach my $bufentry(@bufentries) {
2658 push @result, $bufentry;
2659 }
2660 } else {
2661 my @types = get_fai_types($tmp_classes->{$entry});
2662 foreach my $type (@types) {
2663 push @result,
2664 {
2665 'class' => $entry,
2666 'type' => $type->{'type'},
2667 'release' => $parent,
2668 'state' => $type->{'state'},
2669 };
2670 }
2671 }
2672 }
2673 }
2675 return @result;
2676 }
2678 sub deep_copy {
2679 my $this = shift;
2680 if (not ref $this) {
2681 $this;
2682 } elsif (ref $this eq "ARRAY") {
2683 [map deep_copy($_), @$this];
2684 } elsif (ref $this eq "HASH") {
2685 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2686 } else { die "what type is $_?" }
2687 }
2690 sub session_run_result {
2691 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2692 $kernel->sig(CHLD => "child_reap");
2693 }
2695 sub session_run_debug {
2696 my $result = $_[ARG0];
2697 print STDERR "$result\n";
2698 }
2700 sub session_run_done {
2701 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2702 delete $heap->{task}->{$task_id};
2703 if (exists $heap->{ldap_handle}->{$task_id}) {
2704 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2705 }
2706 delete $heap->{ldap_handle}->{$task_id};
2707 }
2710 sub create_sources_list {
2711 my $session_id = shift || 0;
2712 my $result="/tmp/gosa_si_tmp_sources_list";
2714 # Remove old file
2715 if(stat($result)) {
2716 unlink($result);
2717 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2718 }
2720 my $fh;
2721 open($fh, ">$result");
2722 if (not defined $fh) {
2723 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2724 return undef;
2725 }
2726 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2727 my $ldap_handle = &get_ldap_handle($session_id);
2728 my $mesg=$ldap_handle->search(
2729 base => $main::ldap_server_dn,
2730 scope => 'base',
2731 attrs => 'FAIrepository',
2732 filter => 'objectClass=FAIrepositoryServer'
2733 );
2734 if($mesg->count) {
2735 foreach my $entry(@{$mesg->{'entries'}}) {
2736 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2737 my ($server, $tag, $release, $sections)= split /\|/, $value;
2738 my $line = "deb $server $release";
2739 $sections =~ s/,/ /g;
2740 $line.= " $sections";
2741 print $fh $line."\n";
2742 }
2743 }
2744 }
2745 &release_ldap_handle($ldap_handle);
2746 } else {
2747 if (defined $main::ldap_server_dn){
2748 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2749 } else {
2750 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2751 }
2752 }
2753 close($fh);
2755 return $result;
2756 }
2759 sub run_create_packages_list_db {
2760 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2761 my $session_id = $session->ID;
2762 my $task = POE::Wheel::Run->new(
2763 Priority => +20,
2764 Program => sub {&create_packages_list_db(undef, $session_id)},
2765 StdoutEvent => "session_run_result",
2766 StderrEvent => "session_run_debug",
2767 CloseEvent => "session_run_done",
2768 );
2769 $heap->{task}->{ $task->ID } = $task;
2770 }
2773 sub create_packages_list_db {
2774 my ($sources_file, $session_id) = @_;
2776 # it should not be possible to trigger a recreation of packages_list_db
2777 # while packages_list_db is under construction, so set flag packages_list_under_construction
2778 # which is tested befor recreation can be started
2779 if (-r $packages_list_under_construction) {
2780 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2781 return;
2782 } else {
2783 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2784 # set packages_list_under_construction to true
2785 system("touch $packages_list_under_construction");
2786 @packages_list_statements=();
2787 }
2789 if (not defined $session_id) { $session_id = 0; }
2791 if (not defined $sources_file) {
2792 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2793 $sources_file = &create_sources_list($session_id);
2794 }
2796 if (not defined $sources_file) {
2797 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2798 unlink($packages_list_under_construction);
2799 return;
2800 }
2802 my $line;
2804 open(CONFIG, "<$sources_file") or do {
2805 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2806 unlink($packages_list_under_construction);
2807 return;
2808 };
2810 # Read lines
2811 while ($line = <CONFIG>){
2812 # Unify
2813 chop($line);
2814 $line =~ s/^\s+//;
2815 $line =~ s/^\s+/ /;
2817 # Strip comments
2818 $line =~ s/#.*$//g;
2820 # Skip empty lines
2821 if ($line =~ /^\s*$/){
2822 next;
2823 }
2825 # Interpret deb line
2826 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2827 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2828 my $section;
2829 foreach $section (split(' ', $sections)){
2830 &parse_package_info( $baseurl, $dist, $section, $session_id );
2831 }
2832 }
2833 }
2835 close (CONFIG);
2837 if(keys(%repo_dirs)) {
2838 find(\&cleanup_and_extract, keys( %repo_dirs ));
2839 &main::strip_packages_list_statements();
2840 $packages_list_db->exec_statementlist(\@packages_list_statements);
2841 }
2842 unlink($packages_list_under_construction);
2843 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2844 return;
2845 }
2847 # This function should do some intensive task to minimize the db-traffic
2848 sub strip_packages_list_statements {
2849 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2850 my @new_statement_list=();
2851 my $hash;
2852 my $insert_hash;
2853 my $update_hash;
2854 my $delete_hash;
2855 my $known_packages_hash;
2856 my $local_timestamp=get_time();
2858 foreach my $existing_entry (@existing_entries) {
2859 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2860 }
2862 foreach my $statement (@packages_list_statements) {
2863 if($statement =~ /^INSERT/i) {
2864 # Assign the values from the insert statement
2865 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2866 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2867 if(exists($hash->{$distribution}->{$package}->{$version})) {
2868 # If section or description has changed, update the DB
2869 if(
2870 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2871 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2872 ) {
2873 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2874 } else {
2875 # package is already present in database. cache this knowledge for later use
2876 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2877 }
2878 } else {
2879 # Insert a non-existing entry to db
2880 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2881 }
2882 } elsif ($statement =~ /^UPDATE/i) {
2883 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2884 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2885 foreach my $distribution (keys %{$hash}) {
2886 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2887 # update the insertion hash to execute only one query per package (insert instead insert+update)
2888 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2889 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2890 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2891 my $section;
2892 my $description;
2893 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2894 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2895 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2896 }
2897 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2898 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2899 }
2900 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2901 }
2902 }
2903 }
2904 }
2905 }
2907 # Check for orphaned entries
2908 foreach my $existing_entry (@existing_entries) {
2909 my $distribution= @{$existing_entry}[0];
2910 my $package= @{$existing_entry}[1];
2911 my $version= @{$existing_entry}[2];
2912 my $section= @{$existing_entry}[3];
2914 if(
2915 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2916 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2917 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2918 ) {
2919 next;
2920 } else {
2921 # Insert entry to delete hash
2922 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2923 }
2924 }
2926 # unroll the insert hash
2927 foreach my $distribution (keys %{$insert_hash}) {
2928 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2929 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2930 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2931 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2932 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2933 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2934 ."'$local_timestamp')";
2935 }
2936 }
2937 }
2939 # unroll the update hash
2940 foreach my $distribution (keys %{$update_hash}) {
2941 foreach my $package (keys %{$update_hash->{$distribution}}) {
2942 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2943 my $set = "";
2944 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2945 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2946 }
2947 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2948 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2949 }
2950 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2951 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2952 }
2953 if(defined($set) and length($set) > 0) {
2954 $set .= "timestamp = '$local_timestamp'";
2955 } else {
2956 next;
2957 }
2958 push @new_statement_list,
2959 "UPDATE $main::packages_list_tn SET $set WHERE"
2960 ." distribution = '$distribution'"
2961 ." AND package = '$package'"
2962 ." AND version = '$version'";
2963 }
2964 }
2965 }
2967 # unroll the delete hash
2968 foreach my $distribution (keys %{$delete_hash}) {
2969 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2970 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2971 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2972 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2973 }
2974 }
2975 }
2977 unshift(@new_statement_list, "VACUUM");
2979 @packages_list_statements = @new_statement_list;
2980 }
2983 sub parse_package_info {
2984 my ($baseurl, $dist, $section, $session_id)= @_;
2985 my ($package);
2986 if (not defined $session_id) { $session_id = 0; }
2987 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2988 $repo_dirs{ "${repo_path}/pool" } = 1;
2990 foreach $package ("Packages.gz"){
2991 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2992 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2993 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2994 }
2996 }
2999 sub get_package {
3000 my ($url, $dest, $session_id)= @_;
3001 if (not defined $session_id) { $session_id = 0; }
3003 my $tpath = dirname($dest);
3004 -d "$tpath" || mkpath "$tpath";
3006 # This is ugly, but I've no time to take a look at "how it works in perl"
3007 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3008 system("gunzip -cd '$dest' > '$dest.in'");
3009 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3010 unlink($dest);
3011 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3012 } else {
3013 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3014 }
3015 return 0;
3016 }
3019 sub parse_package {
3020 my ($path, $dist, $srv_path, $session_id)= @_;
3021 if (not defined $session_id) { $session_id = 0;}
3022 my ($package, $version, $section, $description);
3023 my $PACKAGES;
3024 my $timestamp = &get_time();
3026 if(not stat("$path.in")) {
3027 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3028 return;
3029 }
3031 open($PACKAGES, "<$path.in");
3032 if(not defined($PACKAGES)) {
3033 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3034 return;
3035 }
3037 # Read lines
3038 while (<$PACKAGES>){
3039 my $line = $_;
3040 # Unify
3041 chop($line);
3043 # Use empty lines as a trigger
3044 if ($line =~ /^\s*$/){
3045 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3046 push(@packages_list_statements, $sql);
3047 $package = "none";
3048 $version = "none";
3049 $section = "none";
3050 $description = "none";
3051 next;
3052 }
3054 # Trigger for package name
3055 if ($line =~ /^Package:\s/){
3056 ($package)= ($line =~ /^Package: (.*)$/);
3057 next;
3058 }
3060 # Trigger for version
3061 if ($line =~ /^Version:\s/){
3062 ($version)= ($line =~ /^Version: (.*)$/);
3063 next;
3064 }
3066 # Trigger for description
3067 if ($line =~ /^Description:\s/){
3068 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3069 next;
3070 }
3072 # Trigger for section
3073 if ($line =~ /^Section:\s/){
3074 ($section)= ($line =~ /^Section: (.*)$/);
3075 next;
3076 }
3078 # Trigger for filename
3079 if ($line =~ /^Filename:\s/){
3080 my ($filename) = ($line =~ /^Filename: (.*)$/);
3081 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3082 next;
3083 }
3084 }
3086 close( $PACKAGES );
3087 unlink( "$path.in" );
3088 }
3091 sub store_fileinfo {
3092 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3094 my %fileinfo = (
3095 'package' => $package,
3096 'dist' => $dist,
3097 'version' => $vers,
3098 );
3100 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3101 }
3104 sub cleanup_and_extract {
3105 my $fileinfo = $repo_files{ $File::Find::name };
3107 if( defined $fileinfo ) {
3108 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3109 my $sql;
3110 my $package = $fileinfo->{ 'package' };
3111 my $newver = $fileinfo->{ 'version' };
3113 mkpath($dir);
3114 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3116 if( -f "$dir/DEBIAN/templates" ) {
3118 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3120 my $tmpl= ""; {
3121 local $/=undef;
3122 open FILE, "$dir/DEBIAN/templates";
3123 $tmpl = &encode_base64(<FILE>);
3124 close FILE;
3125 }
3126 rmtree("$dir/DEBIAN/templates");
3128 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3129 push @packages_list_statements, $sql;
3130 }
3131 }
3133 return;
3134 }
3137 sub register_at_foreign_servers {
3138 my ($kernel) = $_[KERNEL];
3140 # hole alle bekannten server aus known_server_db
3141 my $server_sql = "SELECT * FROM $known_server_tn";
3142 my $server_res = $known_server_db->exec_statement($server_sql);
3144 # no entries in known_server_db
3145 if (not ref(@$server_res[0]) eq "ARRAY") {
3146 # TODO
3147 }
3149 # detect already connected clients
3150 my $client_sql = "SELECT * FROM $known_clients_tn";
3151 my $client_res = $known_clients_db->exec_statement($client_sql);
3153 # send my server details to all other gosa-si-server within the network
3154 foreach my $hit (@$server_res) {
3155 my $hostname = @$hit[0];
3156 my $hostkey = &create_passwd;
3158 # add already connected clients to registration message
3159 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3160 &add_content2xml_hash($myhash, 'key', $hostkey);
3161 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3163 # add locally loaded gosa-si modules to registration message
3164 my $loaded_modules = {};
3165 while (my ($package, $pck_info) = each %$known_modules) {
3166 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3167 foreach my $act_module (keys(%{@$pck_info[2]})) {
3168 $loaded_modules->{$act_module} = "";
3169 }
3170 }
3172 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3174 # add macaddress to registration message
3175 my ($host_ip, $host_port) = split(/:/, $hostname);
3176 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3177 my $network_interface= &get_interface_for_ip($local_ip);
3178 my $host_mac = &get_mac_for_interface($network_interface);
3179 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3181 # build registration message and send it
3182 my $foreign_server_msg = &create_xml_string($myhash);
3183 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3184 }
3186 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3187 return;
3188 }
3191 #==== MAIN = main ==============================================================
3192 # parse commandline options
3193 Getopt::Long::Configure( "bundling" );
3194 GetOptions("h|help" => \&usage,
3195 "c|config=s" => \$cfg_file,
3196 "f|foreground" => \$foreground,
3197 "v|verbose+" => \$verbose,
3198 "no-arp+" => \$no_arp,
3199 );
3201 # read and set config parameters
3202 &check_cmdline_param ;
3203 &read_configfile($cfg_file, %cfg_defaults);
3204 &check_pid;
3206 $SIG{CHLD} = 'IGNORE';
3208 # forward error messages to logfile
3209 if( ! $foreground ) {
3210 open( STDIN, '+>/dev/null' );
3211 open( STDOUT, '+>&STDIN' );
3212 open( STDERR, '+>&STDIN' );
3213 }
3215 # Just fork, if we are not in foreground mode
3216 if( ! $foreground ) {
3217 chdir '/' or die "Can't chdir to /: $!";
3218 $pid = fork;
3219 setsid or die "Can't start a new session: $!";
3220 umask 0;
3221 } else {
3222 $pid = $$;
3223 }
3225 # Do something useful - put our PID into the pid_file
3226 if( 0 != $pid ) {
3227 open( LOCK_FILE, ">$pid_file" );
3228 print LOCK_FILE "$pid\n";
3229 close( LOCK_FILE );
3230 if( !$foreground ) {
3231 exit( 0 )
3232 };
3233 }
3235 # parse head url and revision from svn
3236 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3237 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3238 $server_headURL = defined $1 ? $1 : 'unknown' ;
3239 $server_revision = defined $2 ? $2 : 'unknown' ;
3240 if ($server_headURL =~ /\/tag\// ||
3241 $server_headURL =~ /\/branches\// ) {
3242 $server_status = "stable";
3243 } else {
3244 $server_status = "developmental" ;
3245 }
3246 # Prepare log file and set permissions
3247 $root_uid = getpwnam('root');
3248 $adm_gid = getgrnam('adm');
3249 open(FH, ">>$log_file");
3250 close FH;
3251 chmod(0440, $log_file);
3252 chown($root_uid, $adm_gid, $log_file);
3253 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3255 daemon_log(" ", 1);
3256 daemon_log("$0 started!", 1);
3257 daemon_log("status: $server_status", 1);
3258 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3260 # Buildup data bases
3261 {
3262 no strict "refs";
3264 if ($db_module eq "DBmysql") {
3265 # connect to incoming_db
3266 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3268 # connect to gosa-si job queue
3269 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3271 # connect to known_clients_db
3272 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3274 # connect to foreign_clients_db
3275 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3277 # connect to known_server_db
3278 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3280 # connect to login_usr_db
3281 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3283 # connect to fai_server_db
3284 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3286 # connect to fai_release_db
3287 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3289 # connect to packages_list_db
3290 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3292 # connect to messaging_db
3293 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3295 } elsif ($db_module eq "DBsqlite") {
3296 # connect to incoming_db
3297 unlink($incoming_file_name);
3298 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3300 # connect to gosa-si job queue
3301 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3302 chmod(0640, $job_queue_file_name);
3303 chown($root_uid, $adm_gid, $job_queue_file_name);
3305 # connect to known_clients_db
3306 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3307 chmod(0640, $known_clients_file_name);
3308 chown($root_uid, $adm_gid, $known_clients_file_name);
3310 # connect to foreign_clients_db
3311 unlink($foreign_clients_file_name);
3312 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3313 chmod(0640, $foreign_clients_file_name);
3314 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3316 # connect to known_server_db
3317 unlink($known_server_file_name);
3318 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3319 chmod(0640, $known_server_file_name);
3320 chown($root_uid, $adm_gid, $known_server_file_name);
3322 # connect to login_usr_db
3323 unlink($login_users_file_name);
3324 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3325 chmod(0640, $login_users_file_name);
3326 chown($root_uid, $adm_gid, $login_users_file_name);
3328 # connect to fai_server_db
3329 unlink($fai_server_file_name);
3330 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3331 chmod(0640, $fai_server_file_name);
3332 chown($root_uid, $adm_gid, $fai_server_file_name);
3334 # connect to fai_release_db
3335 unlink($fai_release_file_name);
3336 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3337 chmod(0640, $fai_release_file_name);
3338 chown($root_uid, $adm_gid, $fai_release_file_name);
3340 # connect to packages_list_db
3341 #unlink($packages_list_file_name);
3342 unlink($packages_list_under_construction);
3343 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3344 chmod(0640, $packages_list_file_name);
3345 chown($root_uid, $adm_gid, $packages_list_file_name);
3347 # connect to messaging_db
3348 unlink($messaging_file_name);
3349 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3350 chmod(0640, $messaging_file_name);
3351 chown($root_uid, $adm_gid, $messaging_file_name);
3352 }
3353 }
3356 # Creating tables
3357 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3358 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3359 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3360 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3361 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3362 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3363 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3364 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3365 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3366 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3368 # create xml object used for en/decrypting
3369 $xml = new XML::Simple();
3372 # foreign servers
3373 my @foreign_server_list;
3375 # add foreign server from cfg file
3376 if ($foreign_server_string ne "") {
3377 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3378 foreach my $foreign_server (@cfg_foreign_server_list) {
3379 push(@foreign_server_list, $foreign_server);
3380 }
3382 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3383 }
3385 # Perform a DNS lookup for server registration if flag is true
3386 if ($dns_lookup eq "true") {
3387 # Add foreign server from dns
3388 my @tmp_servers;
3389 if (not $server_domain) {
3390 # Try our DNS Searchlist
3391 for my $domain(get_dns_domains()) {
3392 chomp($domain);
3393 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3394 if(@$tmp_domains) {
3395 for my $tmp_server(@$tmp_domains) {
3396 push @tmp_servers, $tmp_server;
3397 }
3398 }
3399 }
3400 if(@tmp_servers && length(@tmp_servers)==0) {
3401 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3402 }
3403 } else {
3404 @tmp_servers = &get_server_addresses($server_domain);
3405 if( 0 == @tmp_servers ) {
3406 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3407 }
3408 }
3410 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3412 foreach my $server (@tmp_servers) {
3413 unshift(@foreign_server_list, $server);
3414 }
3415 } else {
3416 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3417 }
3420 # eliminate duplicate entries
3421 @foreign_server_list = &del_doubles(@foreign_server_list);
3422 my $all_foreign_server = join(", ", @foreign_server_list);
3423 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3425 # add all found foreign servers to known_server
3426 my $cur_timestamp = &get_time();
3427 foreach my $foreign_server (@foreign_server_list) {
3429 # do not add myself to known_server_db
3430 if (&is_local($foreign_server)) { next; }
3431 ######################################
3433 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3434 primkey=>['hostname'],
3435 hostname=>$foreign_server,
3436 macaddress=>"",
3437 status=>'not_yet_registered',
3438 hostkey=>"none",
3439 loaded_modules => "none",
3440 timestamp=>$cur_timestamp,
3441 } );
3442 }
3445 # Import all modules
3446 &import_modules;
3448 # Check wether all modules are gosa-si valid passwd check
3449 &password_check;
3451 # Prepare for using Opsi
3452 if ($opsi_enabled eq "true") {
3453 use JSON::RPC::Client;
3454 use XML::Quote qw(:all);
3455 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3456 $opsi_client = new JSON::RPC::Client;
3457 }
3460 POE::Component::Server::TCP->new(
3461 Alias => "TCP_SERVER",
3462 Port => $server_port,
3463 ClientInput => sub {
3464 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3465 my $session_id = $session->ID;
3466 my $remote_ip = $heap->{'remote_ip'};
3467 push(@msgs_to_decrypt, $input);
3468 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3469 $kernel->yield("msg_to_decrypt");
3470 },
3471 InlineStates => {
3472 msg_to_decrypt => \&msg_to_decrypt,
3473 next_task => \&next_task,
3474 task_result => \&handle_task_result,
3475 task_done => \&handle_task_done,
3476 task_debug => \&handle_task_debug,
3477 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3478 }
3479 );
3481 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3483 # create session for repeatedly checking the job queue for jobs
3484 POE::Session->create(
3485 inline_states => {
3486 _start => \&session_start,
3487 register_at_foreign_servers => \®ister_at_foreign_servers,
3488 sig_handler => \&sig_handler,
3489 next_task => \&next_task,
3490 task_result => \&handle_task_result,
3491 task_done => \&handle_task_done,
3492 task_debug => \&handle_task_debug,
3493 watch_for_next_tasks => \&watch_for_next_tasks,
3494 watch_for_new_messages => \&watch_for_new_messages,
3495 watch_for_delivery_messages => \&watch_for_delivery_messages,
3496 watch_for_done_messages => \&watch_for_done_messages,
3497 watch_for_new_jobs => \&watch_for_new_jobs,
3498 watch_for_modified_jobs => \&watch_for_modified_jobs,
3499 watch_for_done_jobs => \&watch_for_done_jobs,
3500 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3501 watch_for_old_known_clients => \&watch_for_old_known_clients,
3502 create_packages_list_db => \&run_create_packages_list_db,
3503 create_fai_server_db => \&run_create_fai_server_db,
3504 create_fai_release_db => \&run_create_fai_release_db,
3505 recreate_packages_db => \&run_recreate_packages_db,
3506 session_run_result => \&session_run_result,
3507 session_run_debug => \&session_run_debug,
3508 session_run_done => \&session_run_done,
3509 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3510 }
3511 );
3514 POE::Kernel->run();
3515 exit;