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