c95660b5ce4aea1257b43745b0b4c0a6a40cace4
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 Cwd;
43 use File::Spec;
44 use File::Basename;
45 use File::Find;
46 use File::Copy;
47 use File::Path;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
52 use Time::HiRes qw( usleep);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
60 my $db_module = "DBsqlite";
61 {
62 no strict "refs";
63 require ("GOSA/".$db_module.".pm");
64 ("GOSA/".$db_module)->import;
65 daemon_log("0 INFO: importing database module '$db_module'", 1);
66 }
68 my $modules_path = "/usr/lib/gosa-si/modules";
69 use lib "/usr/lib/gosa-si/modules";
71 our $global_kernel;
72 my ($foreground, $ping_timeout);
73 my ($server);
74 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
75 my ($messaging_db_loop_delay);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
85 # Variables declared in config file are always set to 'our'
86 our (%cfg_defaults, $log_file, $pid_file,
87 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
88 $arp_activ, $gosa_unit_tag,
89 $GosaPackages_key, $gosa_timeout,
90 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
91 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
92 $arp_enabled, $arp_interface,
93 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
94 $new_systems_ou,
95 );
97 # additional variable which should be globaly accessable
98 our $server_address;
99 our $server_mac_address;
100 our $gosa_address;
101 our $no_arp;
102 our $verbose;
103 our $forground;
104 our $cfg_file;
105 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
106 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
107 our $known_modules;
108 our $root_uid;
109 our $adm_gid;
112 # specifies the verbosity of the daemon_log
113 $verbose = 0 ;
115 # if foreground is not null, script will be not forked to background
116 $foreground = 0 ;
118 # specifies the timeout seconds while checking the online status of a registrating client
119 $ping_timeout = 5;
121 $no_arp = 0;
122 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
123 my @packages_list_statements;
124 my $watch_for_new_jobs_in_progress = 0;
126 # holds all incoming decrypted messages
127 our $incoming_db;
128 our $incoming_tn = 'incoming';
129 my $incoming_file_name;
130 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
131 "timestamp VARCHAR(14) DEFAULT 'none'",
132 "headertag VARCHAR(255) DEFAULT 'none'",
133 "targettag VARCHAR(255) DEFAULT 'none'",
134 "xmlmessage TEXT",
135 "module VARCHAR(255) DEFAULT 'none'",
136 "sessionid VARCHAR(255) DEFAULT '0'",
137 );
139 # holds all gosa jobs
140 our $job_db;
141 our $job_queue_tn = 'jobs';
142 my $job_queue_file_name;
143 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
144 "timestamp VARCHAR(14) DEFAULT 'none'",
145 "status VARCHAR(255) DEFAULT 'none'",
146 "result TEXT",
147 "progress VARCHAR(255) DEFAULT 'none'",
148 "headertag VARCHAR(255) DEFAULT 'none'",
149 "targettag VARCHAR(255) DEFAULT 'none'",
150 "xmlmessage TEXT",
151 "macaddress VARCHAR(17) DEFAULT 'none'",
152 "plainname VARCHAR(255) DEFAULT 'none'",
153 "siserver VARCHAR(255) DEFAULT 'none'",
154 "modified INTEGER DEFAULT '0'",
155 );
157 # holds all other gosa-si-server
158 our $known_server_db;
159 our $known_server_tn = "known_server";
160 my $known_server_file_name;
161 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
163 # holds all registrated clients
164 our $known_clients_db;
165 our $known_clients_tn = "known_clients";
166 my $known_clients_file_name;
167 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)");
169 # holds all registered clients at a foreign server
170 our $foreign_clients_db;
171 our $foreign_clients_tn = "foreign_clients";
172 my $foreign_clients_file_name;
173 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
175 # holds all logged in user at each client
176 our $login_users_db;
177 our $login_users_tn = "login_users";
178 my $login_users_file_name;
179 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
181 # holds all fai server, the debian release and tag
182 our $fai_server_db;
183 our $fai_server_tn = "fai_server";
184 my $fai_server_file_name;
185 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
187 our $fai_release_db;
188 our $fai_release_tn = "fai_release";
189 my $fai_release_file_name;
190 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
192 # holds all packages available from different repositories
193 our $packages_list_db;
194 our $packages_list_tn = "packages_list";
195 my $packages_list_file_name;
196 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
197 my $outdir = "/tmp/packages_list_db";
198 my $arch = "i386";
200 # holds all messages which should be delivered to a user
201 our $messaging_db;
202 our $messaging_tn = "messaging";
203 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
204 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
205 my $messaging_file_name;
207 # path to directory to store client install log files
208 our $client_fai_log_dir = "/var/log/fai";
210 # queue which stores taskes until one of the $max_children children are ready to process the task
211 #my @tasks = qw();
212 my @msgs_to_decrypt = qw();
213 my $max_children = 2;
215 # Allow 50 POE Childs
216 sub MAX_CONCURRENT_TASKS () { 50 }
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 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
253 "max-clients" => [\$max_clients, 10],
254 "wol-password" => [\$wake_on_lan_passwd, ""],
255 "mysql-username" => [\$mysql_username, "gosa_si"],
256 "mysql-password" => [\$mysql_password, ""],
257 "mysql-database" => [\$mysql_database, "gosa_si"],
258 "mysql-host" => [\$mysql_host, "127.0.0.1"],
259 },
260 "GOsaPackages" => {
261 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
262 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
263 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
264 "key" => [\$GosaPackages_key, "none"],
265 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
266 },
267 "ClientPackages" => {
268 "key" => [\$ClientPackages_key, "none"],
269 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
270 },
271 "ServerPackages"=> {
272 "address" => [\$foreign_server_string, ""],
273 "dns-lookup" => [\$dns_lookup, "true"],
274 "domain" => [\$server_domain, ""],
275 "key" => [\$ServerPackages_key, "none"],
276 "key-lifetime" => [\$foreign_servers_register_delay, 120],
277 "job-synchronization-enabled" => [\$job_synchronization, "true"],
278 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
279 },
280 "ArpHandler" => {
281 "enabled" => [\$arp_enabled, "true"],
282 "interface" => [\$arp_interface, "all"],
283 },
284 "Opsi" => {
285 "enabled" => [\$opsi_enabled, "false"],
286 "server" => [\$opsi_server, "localhost"],
287 "admin" => [\$opsi_admin, "opsi-admin"],
288 "password" => [\$opsi_password, "secret"],
289 },
291 );
294 #=== FUNCTION ================================================================
295 # NAME: usage
296 # PARAMETERS: nothing
297 # RETURNS: nothing
298 # DESCRIPTION: print out usage text to STDERR
299 #===============================================================================
300 sub usage {
301 print STDERR << "EOF" ;
302 usage: $prg [-hvf] [-c config]
304 -h : this (help) message
305 -c <file> : config file
306 -f : foreground, process will not be forked to background
307 -v : be verbose (multiple to increase verbosity)
308 -no-arp : starts $prg without connection to arp module
310 EOF
311 print "\n" ;
312 }
315 #=== FUNCTION ================================================================
316 # NAME: logging
317 # PARAMETERS: level - string - default 'info'
318 # msg - string -
319 # facility - string - default 'LOG_DAEMON'
320 # RETURNS: nothing
321 # DESCRIPTION: function for logging
322 #===============================================================================
323 sub daemon_log {
324 # log into log_file
325 my( $msg, $level ) = @_;
326 if(not defined $msg) { return }
327 if(not defined $level) { $level = 1 }
328 if(defined $log_file){
329 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
330 if(not $open_log_fh) {
331 print STDERR "cannot open $log_file: $!";
332 return;
333 }
334 # check owner and group of log_file and update settings if necessary
335 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
336 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
337 chown($root_uid, $adm_gid, $log_file);
338 }
340 chomp($msg);
341 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
342 if($level <= $verbose){
343 my ($seconds, $minutes, $hours, $monthday, $month,
344 $year, $weekday, $yearday, $sommertime) = localtime(time);
345 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
346 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
347 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
348 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
349 $month = $monthnames[$month];
350 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
351 $year+=1900;
352 my $name = $prg;
354 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
355 flock(LOG_HANDLE, LOCK_EX);
356 seek(LOG_HANDLE, 0, 2);
357 print LOG_HANDLE $log_msg;
358 flock(LOG_HANDLE, LOCK_UN);
359 if( $foreground ) {
360 print STDERR $log_msg;
361 }
362 }
363 close( LOG_HANDLE );
364 }
365 }
368 #=== FUNCTION ================================================================
369 # NAME: check_cmdline_param
370 # PARAMETERS: nothing
371 # RETURNS: nothing
372 # DESCRIPTION: validates commandline parameter
373 #===============================================================================
374 sub check_cmdline_param () {
375 my $err_config;
376 my $err_counter = 0;
377 if(not defined($cfg_file)) {
378 $cfg_file = "/etc/gosa-si/server.conf";
379 if(! -r $cfg_file) {
380 $err_config = "please specify a config file";
381 $err_counter += 1;
382 }
383 }
384 if( $err_counter > 0 ) {
385 &usage( "", 1 );
386 if( defined( $err_config)) { print STDERR "$err_config\n"}
387 print STDERR "\n";
388 exit( -1 );
389 }
390 }
393 #=== FUNCTION ================================================================
394 # NAME: check_pid
395 # PARAMETERS: nothing
396 # RETURNS: nothing
397 # DESCRIPTION: handels pid processing
398 #===============================================================================
399 sub check_pid {
400 $pid = -1;
401 # Check, if we are already running
402 if( open(LOCK_FILE, "<$pid_file") ) {
403 $pid = <LOCK_FILE>;
404 if( defined $pid ) {
405 chomp( $pid );
406 if( -f "/proc/$pid/stat" ) {
407 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
408 if( $stat ) {
409 daemon_log("ERROR: Already running",1);
410 close( LOCK_FILE );
411 exit -1;
412 }
413 }
414 }
415 close( LOCK_FILE );
416 unlink( $pid_file );
417 }
419 # create a syslog msg if it is not to possible to open PID file
420 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
421 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
422 if (open(LOCK_FILE, '<', $pid_file)
423 && ($pid = <LOCK_FILE>))
424 {
425 chomp($pid);
426 $msg .= "(PID $pid)\n";
427 } else {
428 $msg .= "(unable to read PID)\n";
429 }
430 if( ! ($foreground) ) {
431 openlog( $0, "cons,pid", "daemon" );
432 syslog( "warning", $msg );
433 closelog();
434 }
435 else {
436 print( STDERR " $msg " );
437 }
438 exit( -1 );
439 }
440 }
442 #=== FUNCTION ================================================================
443 # NAME: import_modules
444 # PARAMETERS: module_path - string - abs. path to the directory the modules
445 # are stored
446 # RETURNS: nothing
447 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
448 # state is on is imported by "require 'file';"
449 #===============================================================================
450 sub import_modules {
451 daemon_log(" ", 1);
453 if (not -e $modules_path) {
454 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
455 }
457 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
458 while (defined (my $file = readdir (DIR))) {
459 if (not $file =~ /(\S*?).pm$/) {
460 next;
461 }
462 my $mod_name = $1;
464 # ArpHandler switch
465 if( $file =~ /ArpHandler.pm/ ) {
466 if( $arp_enabled eq "false" ) { next; }
467 }
469 eval { require $file; };
470 if ($@) {
471 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
472 daemon_log("$@", 1);
473 exit;
474 } else {
475 my $info = eval($mod_name.'::get_module_info()');
476 # Only load module if get_module_info() returns a non-null object
477 if( $info ) {
478 my ($input_address, $input_key, $event_hash) = @{$info};
479 $known_modules->{$mod_name} = $info;
480 daemon_log("0 INFO: module $mod_name loaded", 5);
481 }
482 }
483 }
485 close (DIR);
486 }
488 #=== FUNCTION ================================================================
489 # NAME: password_check
490 # PARAMETERS: nothing
491 # RETURNS: nothing
492 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
493 # the same password
494 #===============================================================================
495 sub password_check {
496 my $passwd_hash = {};
497 while (my ($mod_name, $mod_info) = each %$known_modules) {
498 my $mod_passwd = @$mod_info[1];
499 if (not defined $mod_passwd) { next; }
500 if (not exists $passwd_hash->{$mod_passwd}) {
501 $passwd_hash->{$mod_passwd} = $mod_name;
503 # escalates critical error
504 } else {
505 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
506 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
507 exit( -1 );
508 }
509 }
511 }
514 #=== FUNCTION ================================================================
515 # NAME: sig_int_handler
516 # PARAMETERS: signal - string - signal arose from system
517 # RETURNS: nothing
518 # DESCRIPTION: handels tasks to be done befor signal becomes active
519 #===============================================================================
520 sub sig_int_handler {
521 my ($signal) = @_;
523 # if (defined($ldap_handle)) {
524 # $ldap_handle->disconnect;
525 # }
526 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
529 daemon_log("shutting down gosa-si-server", 1);
530 system("kill `ps -C gosa-si-server -o pid=`");
531 }
532 $SIG{INT} = \&sig_int_handler;
535 sub check_key_and_xml_validity {
536 my ($crypted_msg, $module_key, $session_id) = @_;
537 my $msg;
538 my $msg_hash;
539 my $error_string;
540 eval{
541 $msg = &decrypt_msg($crypted_msg, $module_key);
543 if ($msg =~ /<xml>/i){
544 $msg =~ s/\s+/ /g; # just for better daemon_log
545 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
546 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
548 ##############
549 # check header
550 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
551 my $header_l = $msg_hash->{'header'};
552 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
553 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
554 my $header = @{$header_l}[0];
555 if( 0 == length $header) { die 'empty string in header tag'; }
557 ##############
558 # check source
559 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
560 my $source_l = $msg_hash->{'source'};
561 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
562 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
563 my $source = @{$source_l}[0];
564 if( 0 == length $source) { die 'source error'; }
566 ##############
567 # check target
568 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
569 my $target_l = $msg_hash->{'target'};
570 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
571 }
572 };
573 if($@) {
574 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
575 $msg = undef;
576 $msg_hash = undef;
577 }
579 return ($msg, $msg_hash);
580 }
583 sub check_outgoing_xml_validity {
584 my ($msg, $session_id) = @_;
586 my $msg_hash;
587 eval{
588 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
590 ##############
591 # check header
592 my $header_l = $msg_hash->{'header'};
593 if( 1 != @{$header_l} ) {
594 die 'no or more than one headers specified';
595 }
596 my $header = @{$header_l}[0];
597 if( 0 == length $header) {
598 die 'header has length 0';
599 }
601 ##############
602 # check source
603 my $source_l = $msg_hash->{'source'};
604 if( 1 != @{$source_l} ) {
605 die 'no or more than 1 sources specified';
606 }
607 my $source = @{$source_l}[0];
608 if( 0 == length $source) {
609 die 'source has length 0';
610 }
612 # Check if source contains hostname instead of ip address
613 if($source =~ /^[a-z][a-z0-9\.]+:\d+$/i) {
614 my ($hostname,$port) = split(/:/, $source);
615 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
616 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
617 # Write ip address to $source variable
618 $source = "$ip_address:$port";
619 }
620 }
621 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
622 $source =~ /^GOSA$/i) {
623 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
624 }
626 ##############
627 # check target
628 my $target_l = $msg_hash->{'target'};
629 if( 0 == @{$target_l} ) {
630 die "no targets specified";
631 }
632 foreach my $target (@$target_l) {
633 if( 0 == length $target) {
634 die "target has length 0";
635 }
636 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
637 $target =~ /^GOSA$/i ||
638 $target =~ /^\*$/ ||
639 $target =~ /KNOWN_SERVER/i ||
640 $target =~ /JOBDB/i ||
641 $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 ){
642 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
643 }
644 }
645 };
646 if($@) {
647 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
648 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
649 $msg_hash = undef;
650 }
652 return ($msg_hash);
653 }
656 sub input_from_known_server {
657 my ($input, $remote_ip, $session_id) = @_ ;
658 my ($msg, $msg_hash, $module);
660 my $sql_statement= "SELECT * FROM known_server";
661 my $query_res = $known_server_db->select_dbentry( $sql_statement );
663 while( my ($hit_num, $hit) = each %{ $query_res } ) {
664 my $host_name = $hit->{hostname};
665 if( not $host_name =~ "^$remote_ip") {
666 next;
667 }
668 my $host_key = $hit->{hostkey};
669 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
670 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
672 # check if module can open msg envelope with module key
673 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
674 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
675 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
676 daemon_log("$@", 8);
677 next;
678 }
679 else {
680 $msg = $tmp_msg;
681 $msg_hash = $tmp_msg_hash;
682 $module = "ServerPackages";
683 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
684 last;
685 }
686 }
688 if( (!$msg) || (!$msg_hash) || (!$module) ) {
689 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
690 }
692 return ($msg, $msg_hash, $module);
693 }
696 sub input_from_known_client {
697 my ($input, $remote_ip, $session_id) = @_ ;
698 my ($msg, $msg_hash, $module);
700 my $sql_statement= "SELECT * FROM known_clients";
701 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
702 while( my ($hit_num, $hit) = each %{ $query_res } ) {
703 my $host_name = $hit->{hostname};
704 if( not $host_name =~ /^$remote_ip:\d*$/) {
705 next;
706 }
707 my $host_key = $hit->{hostkey};
708 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
709 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
711 # check if module can open msg envelope with module key
712 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
714 if( (!$msg) || (!$msg_hash) ) {
715 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
716 &daemon_log("$@", 8);
717 next;
718 }
719 else {
720 $module = "ClientPackages";
721 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
722 last;
723 }
724 }
726 if( (!$msg) || (!$msg_hash) || (!$module) ) {
727 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
728 }
730 return ($msg, $msg_hash, $module);
731 }
734 sub input_from_unknown_host {
735 no strict "refs";
736 my ($input, $session_id) = @_ ;
737 my ($msg, $msg_hash, $module);
738 my $error_string;
740 my %act_modules = %$known_modules;
742 while( my ($mod, $info) = each(%act_modules)) {
744 # check a key exists for this module
745 my $module_key = ${$mod."_key"};
746 if( not defined $module_key ) {
747 if( $mod eq 'ArpHandler' ) {
748 next;
749 }
750 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
751 next;
752 }
753 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
755 # check if module can open msg envelope with module key
756 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
757 if( (not defined $msg) || (not defined $msg_hash) ) {
758 next;
759 } else {
760 $module = $mod;
761 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
762 last;
763 }
764 }
766 if( (!$msg) || (!$msg_hash) || (!$module)) {
767 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
768 }
770 return ($msg, $msg_hash, $module);
771 }
774 sub create_ciphering {
775 my ($passwd) = @_;
776 if((!defined($passwd)) || length($passwd)==0) {
777 $passwd = "";
778 }
779 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
780 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
781 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
782 $my_cipher->set_iv($iv);
783 return $my_cipher;
784 }
787 sub encrypt_msg {
788 my ($msg, $key) = @_;
789 my $my_cipher = &create_ciphering($key);
790 my $len;
791 {
792 use bytes;
793 $len= 16-length($msg)%16;
794 }
795 $msg = "\0"x($len).$msg;
796 $msg = $my_cipher->encrypt($msg);
797 chomp($msg = &encode_base64($msg));
798 # there are no newlines allowed inside msg
799 $msg=~ s/\n//g;
800 return $msg;
801 }
804 sub decrypt_msg {
806 my ($msg, $key) = @_ ;
807 $msg = &decode_base64($msg);
808 my $my_cipher = &create_ciphering($key);
809 $msg = $my_cipher->decrypt($msg);
810 $msg =~ s/\0*//g;
811 return $msg;
812 }
815 sub get_encrypt_key {
816 my ($target) = @_ ;
817 my $encrypt_key;
818 my $error = 0;
820 # target can be in known_server
821 if( not defined $encrypt_key ) {
822 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
823 my $query_res = $known_server_db->select_dbentry( $sql_statement );
824 while( my ($hit_num, $hit) = each %{ $query_res } ) {
825 my $host_name = $hit->{hostname};
826 if( $host_name ne $target ) {
827 next;
828 }
829 $encrypt_key = $hit->{hostkey};
830 last;
831 }
832 }
834 # target can be in known_client
835 if( not defined $encrypt_key ) {
836 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
837 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
838 while( my ($hit_num, $hit) = each %{ $query_res } ) {
839 my $host_name = $hit->{hostname};
840 if( $host_name ne $target ) {
841 next;
842 }
843 $encrypt_key = $hit->{hostkey};
844 last;
845 }
846 }
848 return $encrypt_key;
849 }
852 #=== FUNCTION ================================================================
853 # NAME: open_socket
854 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
855 # [PeerPort] string necessary if port not appended by PeerAddr
856 # RETURNS: socket IO::Socket::INET
857 # DESCRIPTION: open a socket to PeerAddr
858 #===============================================================================
859 sub open_socket {
860 my ($PeerAddr, $PeerPort) = @_ ;
861 if(defined($PeerPort)){
862 $PeerAddr = $PeerAddr.":".$PeerPort;
863 }
864 my $socket;
865 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
866 Porto => "tcp",
867 Type => SOCK_STREAM,
868 Timeout => 5,
869 );
870 if(not defined $socket) {
871 return;
872 }
873 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
874 return $socket;
875 }
878 sub send_msg_to_target {
879 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
880 my $error = 0;
881 my $header;
882 my $timestamp = &get_time();
883 my $new_status;
884 my $act_status;
885 my ($sql_statement, $res);
887 if( $msg_header ) {
888 $header = "'$msg_header'-";
889 } else {
890 $header = "";
891 }
893 # Patch the source ip
894 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
895 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
896 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
897 }
899 # encrypt xml msg
900 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
902 # opensocket
903 my $socket = &open_socket($address);
904 if( !$socket ) {
905 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
906 $error++;
907 }
909 if( $error == 0 ) {
910 # send xml msg
911 print $socket $crypted_msg."\n";
913 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
914 daemon_log("$session_id DEBUG: message:\n$msg", 9);
916 }
918 # close socket in any case
919 if( $socket ) {
920 close $socket;
921 }
923 if( $error > 0 ) { $new_status = "down"; }
924 else { $new_status = $msg_header; }
927 # known_clients
928 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
929 $res = $known_clients_db->select_dbentry($sql_statement);
930 if( keys(%$res) == 1) {
931 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
932 if ($act_status eq "down" && $new_status eq "down") {
933 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
934 $res = $known_clients_db->del_dbentry($sql_statement);
935 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
936 } else {
937 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
938 $res = $known_clients_db->update_dbentry($sql_statement);
939 if($new_status eq "down"){
940 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
941 } else {
942 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
943 }
944 }
945 }
947 # known_server
948 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
949 $res = $known_server_db->select_dbentry($sql_statement);
950 if( keys(%$res) == 1) {
951 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
952 if ($act_status eq "down" && $new_status eq "down") {
953 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
954 $res = $known_server_db->del_dbentry($sql_statement);
955 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
957 # Remove the registered clients of the server as well
958 $sql_statement = "DELETE FROM foreign_clients WHERE regserver='$address'";
959 $res = $foreign_clients_db->del_dbentry($sql_statement);
960 }
961 else {
962 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
963 $res = $known_server_db->update_dbentry($sql_statement);
964 if($new_status eq "down"){
965 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
966 } else {
967 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
968 }
969 }
970 }
971 return $error;
972 }
975 sub update_jobdb_status_for_send_msgs {
976 my ($session_id, $answer, $error) = @_;
977 &daemon_log("$session_id DEBUG: try to update job status", 7);
978 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
979 my $jobdb_id = $1;
981 $answer =~ /<header>(.*)<\/header>/;
982 my $job_header = $1;
984 $answer =~ /<target>(.*)<\/target>/;
985 my $job_target = $1;
987 # Sending msg failed
988 if( $error ) {
990 # Set jobs to done, jobs do not need to deliver their message in any case
991 if (($job_header eq "trigger_action_localboot")
992 ||($job_header eq "trigger_action_lock")
993 ||($job_header eq "trigger_action_halt")
994 ) {
995 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
996 &daemon_log("$session_id DEBUG: $sql_statement", 7);
997 my $res = $job_db->update_dbentry($sql_statement);
999 # Reactivate jobs, jobs need to deliver their message
1000 } elsif (($job_header eq "trigger_action_activate")
1001 ||($job_header eq "trigger_action_update")
1002 ||($job_header eq "trigger_action_reinstall")
1003 ||($job_header eq "trigger_activate_new")
1004 ) {
1005 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1007 # For all other messages
1008 } else {
1009 my $sql_statement = "UPDATE $job_queue_tn ".
1010 "SET status='error', result='can not deliver msg, please consult log file' ".
1011 "WHERE id=$jobdb_id";
1012 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1013 my $res = $job_db->update_dbentry($sql_statement);
1014 }
1016 # Sending msg was successful
1017 } else {
1018 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1019 # jobs reinstall, update, inst_update do themself setting to done
1020 if (($job_header eq "trigger_action_localboot")
1021 ||($job_header eq "trigger_action_lock")
1022 ||($job_header eq "trigger_action_activate")
1023 ||($job_header eq "trigger_action_halt")
1024 ||($job_header eq "trigger_action_reboot")
1025 ||($job_header eq "trigger_action_wake")
1026 ||($job_header eq "trigger_wake")
1027 ) {
1029 my $sql_statement = "UPDATE $job_queue_tn ".
1030 "SET status='done' ".
1031 "WHERE id=$jobdb_id AND status='processed'";
1032 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1033 my $res = $job_db->update_dbentry($sql_statement);
1034 } else {
1035 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7);
1036 }
1037 }
1038 } else {
1039 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7);
1040 }
1041 }
1043 sub reactivate_job_with_delay {
1044 my ($session_id, $target, $header, $delay) = @_ ;
1045 # 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
1047 if (not defined $delay) { $delay = 30 } ;
1048 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1050 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1051 my $res = $job_db->update_dbentry($sql);
1052 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1053 "cause client '$target' is currently not available", 5);
1054 daemon_log("$session_id $sql", 7);
1055 return;
1056 }
1059 sub sig_handler {
1060 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1061 daemon_log("0 INFO got signal '$signal'", 1);
1062 $kernel->sig_handled();
1063 return;
1064 }
1067 sub msg_to_decrypt {
1068 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1069 my $session_id = $session->ID;
1070 my ($msg, $msg_hash, $module);
1071 my $error = 0;
1073 # fetch new msg out of @msgs_to_decrypt
1074 my $tmp_next_msg = shift @msgs_to_decrypt;
1075 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1077 # msg is from a new client or gosa
1078 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1080 # msg is from a gosa-si-server
1081 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1082 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1083 }
1084 # msg is from a gosa-si-client
1085 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1086 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1087 }
1088 # an error occurred
1089 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1090 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1091 # could not understand a msg from its server the client cause a re-registering process
1092 my $remote_ip = $heap->{'remote_ip'};
1093 my $remote_port = $heap->{'remote_port'};
1094 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1095 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1097 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1098 "' to cause a re-registering of the client if necessary", 3);
1099 $error++;
1100 }
1103 my $header;
1104 my $target;
1105 my $source;
1106 my $done = 0;
1107 my $sql;
1108 my $res;
1110 # check whether this message should be processed here
1111 if ($error == 0) {
1112 $header = @{$msg_hash->{'header'}}[0];
1113 $target = @{$msg_hash->{'target'}}[0];
1114 $source = @{$msg_hash->{'source'}}[0];
1115 my $not_found_in_known_clients_db = 0;
1116 my $not_found_in_known_server_db = 0;
1117 my $not_found_in_foreign_clients_db = 0;
1118 my $local_address;
1119 my $local_mac;
1120 my ($target_ip, $target_port) = split(':', $target);
1122 # Determine the local ip address if target is an ip address
1123 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1124 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1125 } else {
1126 $local_address = $server_address;
1127 }
1129 # Determine the local mac address if target is a mac address
1130 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) {
1131 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1132 my $network_interface= &get_interface_for_ip($loc_ip);
1133 $local_mac = &get_mac_for_interface($network_interface);
1134 } else {
1135 $local_mac = $server_mac_address;
1136 }
1138 # target and source is equal to GOSA -> process here
1139 if (not $done) {
1140 if ($target eq "GOSA" && $source eq "GOSA") {
1141 $done = 1;
1142 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1143 }
1144 }
1146 # target is own address without forward_to_gosa-tag -> process here
1147 if (not $done) {
1148 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1149 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1150 $done = 1;
1151 if ($source eq "GOSA") {
1152 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1153 }
1154 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1155 }
1156 }
1158 # target is a client address in known_clients -> process here
1159 if (not $done) {
1160 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1161 $res = $known_clients_db->select_dbentry($sql);
1162 if (keys(%$res) > 0) {
1163 $done = 1;
1164 my $hostname = $res->{1}->{'hostname'};
1165 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1166 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1167 if ($source eq "GOSA") {
1168 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1169 }
1170 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1172 } else {
1173 $not_found_in_known_clients_db = 1;
1174 }
1175 }
1177 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1178 if (not $done) {
1179 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1180 my $gosa_at;
1181 my $gosa_session_id;
1182 if (($target eq $local_address) && (defined $forward_to_gosa)){
1183 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1184 if ($gosa_at ne $local_address) {
1185 $done = 1;
1186 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1187 }
1188 }
1189 }
1191 # if message should be processed here -> add message to incoming_db
1192 if ($done) {
1193 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1194 # so gosa-si-server knows how to process this kind of messages
1195 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1196 $module = "GosaPackages";
1197 }
1199 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1200 primkey=>[],
1201 headertag=>$header,
1202 targettag=>$target,
1203 xmlmessage=>&encode_base64($msg),
1204 timestamp=>&get_time,
1205 module=>$module,
1206 sessionid=>$session_id,
1207 } );
1209 }
1211 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1212 if (not $done) {
1213 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1214 my $gosa_at;
1215 my $gosa_session_id;
1216 if (($target eq $local_address) && (defined $forward_to_gosa)){
1217 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1218 if ($gosa_at eq $local_address) {
1219 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1220 if( defined $session_reference ) {
1221 $heap = $session_reference->get_heap();
1222 }
1223 if(exists $heap->{'client'}) {
1224 $msg = &encrypt_msg($msg, $GosaPackages_key);
1225 $heap->{'client'}->put($msg);
1226 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1227 }
1228 $done = 1;
1229 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1230 }
1231 }
1233 }
1235 # target is a client address in foreign_clients -> forward to registration server
1236 if (not $done) {
1237 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1238 $res = $foreign_clients_db->select_dbentry($sql);
1239 if (keys(%$res) > 0) {
1240 my $hostname = $res->{1}->{'hostname'};
1241 my ($host_ip, $host_port) = split(/:/, $hostname);
1242 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1243 my $regserver = $res->{1}->{'regserver'};
1244 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1245 my $res = $known_server_db->select_dbentry($sql);
1246 if (keys(%$res) > 0) {
1247 my $regserver_key = $res->{1}->{'hostkey'};
1248 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1249 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1250 if ($source eq "GOSA") {
1251 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1252 }
1253 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1254 }
1255 $done = 1;
1256 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1257 } else {
1258 $not_found_in_foreign_clients_db = 1;
1259 }
1260 }
1262 # target is a server address -> forward to server
1263 if (not $done) {
1264 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1265 $res = $known_server_db->select_dbentry($sql);
1266 if (keys(%$res) > 0) {
1267 my $hostkey = $res->{1}->{'hostkey'};
1269 if ($source eq "GOSA") {
1270 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1271 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1273 }
1275 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1276 $done = 1;
1277 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1278 } else {
1279 $not_found_in_known_server_db = 1;
1280 }
1281 }
1284 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1285 if ( $not_found_in_foreign_clients_db
1286 && $not_found_in_known_server_db
1287 && $not_found_in_known_clients_db) {
1288 &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);
1289 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1290 $module = "GosaPackages";
1291 }
1292 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1293 primkey=>[],
1294 headertag=>$header,
1295 targettag=>$target,
1296 xmlmessage=>&encode_base64($msg),
1297 timestamp=>&get_time,
1298 module=>$module,
1299 sessionid=>$session_id,
1300 } );
1301 $done = 1;
1302 }
1305 if (not $done) {
1306 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1307 if ($source eq "GOSA") {
1308 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1309 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1311 my $session_reference = $kernel->ID_id_to_session($session_id);
1312 if( defined $session_reference ) {
1313 $heap = $session_reference->get_heap();
1314 }
1315 if(exists $heap->{'client'}) {
1316 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1317 $heap->{'client'}->put($error_msg);
1318 }
1319 }
1320 }
1322 }
1324 return;
1325 }
1328 sub next_task {
1329 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1330 my $running_task = POE::Wheel::Run->new(
1331 Program => sub { process_task($session, $heap, $task) },
1332 StdioFilter => POE::Filter::Reference->new(),
1333 StdoutEvent => "task_result",
1334 StderrEvent => "task_debug",
1335 CloseEvent => "task_done",
1336 );
1337 $heap->{task}->{ $running_task->ID } = $running_task;
1338 }
1340 sub handle_task_result {
1341 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1342 my $client_answer = $result->{'answer'};
1343 if( $client_answer =~ s/session_id=(\d+)$// ) {
1344 my $session_id = $1;
1345 if( defined $session_id ) {
1346 my $session_reference = $kernel->ID_id_to_session($session_id);
1347 if( defined $session_reference ) {
1348 $heap = $session_reference->get_heap();
1349 }
1350 }
1352 if(exists $heap->{'client'}) {
1353 $heap->{'client'}->put($client_answer);
1354 }
1355 }
1356 $kernel->sig(CHLD => "child_reap");
1357 }
1359 sub handle_task_debug {
1360 my $result = $_[ARG0];
1361 print STDERR "$result\n";
1362 }
1364 sub handle_task_done {
1365 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1366 delete $heap->{task}->{$task_id};
1367 }
1369 sub process_task {
1370 no strict "refs";
1371 #CHECK: Not @_[...]?
1372 my ($session, $heap, $task) = @_;
1373 my $error = 0;
1374 my $answer_l;
1375 my ($answer_header, @answer_target_l, $answer_source);
1376 my $client_answer = "";
1378 # prepare all variables needed to process message
1379 #my $msg = $task->{'xmlmessage'};
1380 my $msg = &decode_base64($task->{'xmlmessage'});
1381 my $incoming_id = $task->{'id'};
1382 my $module = $task->{'module'};
1383 my $header = $task->{'headertag'};
1384 my $session_id = $task->{'sessionid'};
1385 my $msg_hash;
1386 eval {
1387 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1388 };
1389 daemon_log("ERROR: XML failure '$@'") if ($@);
1390 my $source = @{$msg_hash->{'source'}}[0];
1392 # set timestamp of incoming client uptodate, so client will not
1393 # be deleted from known_clients because of expiration
1394 my $cur_time = &get_time();
1395 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1396 my $res = $known_clients_db->exec_statement($sql);
1398 ######################
1399 # process incoming msg
1400 if( $error == 0) {
1401 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1402 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1403 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1405 if ( 0 < @{$answer_l} ) {
1406 my $answer_str = join("\n", @{$answer_l});
1407 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1408 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1409 }
1410 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1411 } else {
1412 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1413 }
1415 }
1416 if( !$answer_l ) { $error++ };
1418 ########
1419 # answer
1420 if( $error == 0 ) {
1422 foreach my $answer ( @{$answer_l} ) {
1423 # check outgoing msg to xml validity
1424 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1425 if( not defined $answer_hash ) { next; }
1427 $answer_header = @{$answer_hash->{'header'}}[0];
1428 @answer_target_l = @{$answer_hash->{'target'}};
1429 $answer_source = @{$answer_hash->{'source'}}[0];
1431 # deliver msg to all targets
1432 foreach my $answer_target ( @answer_target_l ) {
1434 # targets of msg are all gosa-si-clients in known_clients_db
1435 if( $answer_target eq "*" ) {
1436 # answer is for all clients
1437 my $sql_statement= "SELECT * FROM known_clients";
1438 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1439 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1440 my $host_name = $hit->{hostname};
1441 my $host_key = $hit->{hostkey};
1442 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1443 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1444 }
1445 }
1447 # targets of msg are all gosa-si-server in known_server_db
1448 elsif( $answer_target eq "KNOWN_SERVER" ) {
1449 # answer is for all server in known_server
1450 my $sql_statement= "SELECT * FROM $known_server_tn";
1451 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1452 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1453 my $host_name = $hit->{hostname};
1454 my $host_key = $hit->{hostkey};
1455 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1456 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1457 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1458 }
1459 }
1461 # target of msg is GOsa
1462 elsif( $answer_target eq "GOSA" ) {
1463 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1464 my $add_on = "";
1465 if( defined $session_id ) {
1466 $add_on = ".session_id=$session_id";
1467 }
1468 # answer is for GOSA and has to returned to connected client
1469 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1470 $client_answer = $gosa_answer.$add_on;
1471 }
1473 # target of msg is job queue at this host
1474 elsif( $answer_target eq "JOBDB") {
1475 $answer =~ /<header>(\S+)<\/header>/;
1476 my $header;
1477 if( defined $1 ) { $header = $1; }
1478 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1479 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1480 }
1482 # Target of msg is a mac address
1483 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 ) {
1484 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1486 # Looking for macaddress in known_clients
1487 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1488 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1489 my $found_ip_flag = 0;
1490 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1491 my $host_name = $hit->{hostname};
1492 my $host_key = $hit->{hostkey};
1493 $answer =~ s/$answer_target/$host_name/g;
1494 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1495 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1496 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1497 $found_ip_flag++ ;
1498 }
1500 # Looking for macaddress in foreign_clients
1501 if ($found_ip_flag == 0) {
1502 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1503 my $res = $foreign_clients_db->select_dbentry($sql);
1504 while( my ($hit_num, $hit) = each %{ $res } ) {
1505 my $host_name = $hit->{hostname};
1506 my $reg_server = $hit->{regserver};
1507 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1509 # Fetch key for reg_server
1510 my $reg_server_key;
1511 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1512 my $res = $known_server_db->select_dbentry($sql);
1513 if (exists $res->{1}) {
1514 $reg_server_key = $res->{1}->{'hostkey'};
1515 } else {
1516 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1517 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1518 $reg_server_key = undef;
1519 }
1521 # Send answer to server where client is registered
1522 if (defined $reg_server_key) {
1523 $answer =~ s/$answer_target/$host_name/g;
1524 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1525 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1526 $found_ip_flag++ ;
1527 }
1528 }
1529 }
1531 # No mac to ip matching found
1532 if( $found_ip_flag == 0) {
1533 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1534 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1535 }
1537 # Answer is for one specific host
1538 } else {
1539 # get encrypt_key
1540 my $encrypt_key = &get_encrypt_key($answer_target);
1541 if( not defined $encrypt_key ) {
1542 # unknown target
1543 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1544 next;
1545 }
1546 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1547 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1548 }
1549 }
1550 }
1551 }
1553 my $filter = POE::Filter::Reference->new();
1554 my %result = (
1555 status => "seems ok to me",
1556 answer => $client_answer,
1557 );
1559 my $output = $filter->put( [ \%result ] );
1560 print @$output;
1563 }
1565 sub session_start {
1566 my ($kernel) = $_[KERNEL];
1567 $global_kernel = $kernel;
1568 $kernel->yield('register_at_foreign_servers');
1569 $kernel->yield('create_fai_server_db', $fai_server_tn );
1570 $kernel->yield('create_fai_release_db', $fai_release_tn );
1571 $kernel->yield('watch_for_next_tasks');
1572 $kernel->sig(USR1 => "sig_handler");
1573 $kernel->sig(USR2 => "recreate_packages_db");
1574 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1575 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1576 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1577 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1578 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1579 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1580 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1582 # Start opsi check
1583 if ($opsi_enabled eq "true") {
1584 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1585 }
1587 }
1590 sub watch_for_done_jobs {
1591 #CHECK: $heap for what?
1592 my ($kernel,$heap) = @_[KERNEL, HEAP];
1594 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1595 my $res = $job_db->select_dbentry( $sql_statement );
1597 while( my ($id, $hit) = each %{$res} ) {
1598 my $jobdb_id = $hit->{id};
1599 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1600 my $res = $job_db->del_dbentry($sql_statement);
1601 }
1603 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1604 }
1607 sub watch_for_opsi_jobs {
1608 my ($kernel) = $_[KERNEL];
1610 # 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
1611 # opsi install job is to parse the xml message. There is still the correct header.
1612 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1613 my $res = $job_db->select_dbentry( $sql_statement );
1615 # Ask OPSI for an update of the running jobs
1616 while (my ($id, $hit) = each %$res ) {
1617 # Determine current parameters of the job
1618 my $hostId = $hit->{'plainname'};
1619 my $macaddress = $hit->{'macaddress'};
1620 my $progress = $hit->{'progress'};
1622 my $result= {};
1624 # For hosts, only return the products that are or get installed
1625 my $callobj;
1626 $callobj = {
1627 method => 'getProductStates_hash',
1628 params => [ $hostId ],
1629 id => 1,
1630 };
1632 my $hres = $opsi_client->call($opsi_url, $callobj);
1633 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1634 if (not &check_opsi_res($hres)) {
1635 my $htmp= $hres->result->{$hostId};
1637 # Check state != not_installed or action == setup -> load and add
1638 my $products= 0;
1639 my $installed= 0;
1640 my $installing = 0;
1641 my $error= 0;
1642 my @installed_list;
1643 my @error_list;
1644 my $act_status = "none";
1645 foreach my $product (@{$htmp}){
1647 if ($product->{'installationStatus'} ne "not_installed" or
1648 $product->{'actionRequest'} eq "setup"){
1650 # Increase number of products for this host
1651 $products++;
1653 if ($product->{'installationStatus'} eq "failed"){
1654 $result->{$product->{'productId'}}= "error";
1655 unshift(@error_list, $product->{'productId'});
1656 $error++;
1657 }
1658 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1659 $result->{$product->{'productId'}}= "installed";
1660 unshift(@installed_list, $product->{'productId'});
1661 $installed++;
1662 }
1663 if ($product->{'installationStatus'} eq "installing"){
1664 $result->{$product->{'productId'}}= "installing";
1665 $installing++;
1666 $act_status = "installing - ".$product->{'productId'};
1667 }
1668 }
1669 }
1671 # Estimate "rough" progress, avoid division by zero
1672 if ($products == 0) {
1673 $result->{'progress'}= 0;
1674 } else {
1675 $result->{'progress'}= int($installed * 100 / $products);
1676 }
1678 # Set updates in job queue
1679 if ((not $error) && (not $installing) && ($installed)) {
1680 $act_status = "installed - ".join(", ", @installed_list);
1681 }
1682 if ($error) {
1683 $act_status = "error - ".join(", ", @error_list);
1684 }
1685 if ($progress ne $result->{'progress'} ) {
1686 # Updating progress and result
1687 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1688 my $update_res = $job_db->update_dbentry($update_statement);
1689 }
1690 if ($progress eq 100) {
1691 # Updateing status
1692 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1693 if ($error) {
1694 $done_statement .= "status='error'";
1695 } else {
1696 $done_statement .= "status='done'";
1697 }
1698 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1699 my $done_res = $job_db->update_dbentry($done_statement);
1700 }
1703 }
1704 }
1706 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1707 }
1710 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1711 sub watch_for_modified_jobs {
1712 my ($kernel,$heap) = @_[KERNEL, HEAP];
1714 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1715 my $res = $job_db->select_dbentry( $sql_statement );
1717 # if db contains no jobs which should be update, do nothing
1718 if (keys %$res != 0) {
1720 if ($job_synchronization eq "true") {
1721 # make out of the db result a gosa-si message
1722 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1724 # update all other SI-server
1725 &inform_all_other_si_server($update_msg);
1726 }
1728 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1729 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1730 $res = $job_db->update_dbentry($sql_statement);
1731 }
1733 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1734 }
1737 sub watch_for_new_jobs {
1738 if($watch_for_new_jobs_in_progress == 0) {
1739 $watch_for_new_jobs_in_progress = 1;
1740 my ($kernel,$heap) = @_[KERNEL, HEAP];
1742 # check gosa job quaeue for jobs with executable timestamp
1743 my $timestamp = &get_time();
1744 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1745 my $res = $job_db->exec_statement( $sql_statement );
1747 # Merge all new jobs that would do the same actions
1748 my @drops;
1749 my $hits;
1750 foreach my $hit (reverse @{$res} ) {
1751 my $macaddress= lc @{$hit}[8];
1752 my $headertag= @{$hit}[5];
1753 if(
1754 defined($hits->{$macaddress}) &&
1755 defined($hits->{$macaddress}->{$headertag}) &&
1756 defined($hits->{$macaddress}->{$headertag}[0])
1757 ) {
1758 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1759 }
1760 $hits->{$macaddress}->{$headertag}= $hit;
1761 }
1763 # Delete new jobs with a matching job in state 'processing'
1764 foreach my $macaddress (keys %{$hits}) {
1765 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1766 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1767 if(defined($jobdb_id)) {
1768 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1769 my $res = $job_db->exec_statement( $sql_statement );
1770 foreach my $hit (@{$res}) {
1771 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1772 }
1773 } else {
1774 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1775 }
1776 }
1777 }
1779 # Commit deletion
1780 $job_db->exec_statementlist(\@drops);
1782 # Look for new jobs that could be executed
1783 foreach my $macaddress (keys %{$hits}) {
1785 # Look if there is an executing job
1786 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1787 my $res = $job_db->exec_statement( $sql_statement );
1789 # Skip new jobs for host if there is a processing job
1790 if(defined($res) and defined @{$res}[0]) {
1791 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1792 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1793 if(@{$row}[5] eq 'trigger_action_reinstall') {
1794 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1795 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1796 if(defined($res_2) and defined @{$res_2}[0]) {
1797 # Set status from goto-activation to 'waiting' and update timestamp
1798 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting', timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1799 }
1800 }
1801 next;
1802 }
1804 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1805 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1806 if(defined($jobdb_id)) {
1807 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1809 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1810 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1811 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1813 # expect macaddress is unique!!!!!!
1814 my $target = $res_hash->{1}->{hostname};
1816 # change header
1817 $job_msg =~ s/<header>job_/<header>gosa_/;
1819 # add sqlite_id
1820 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1822 $job_msg =~ /<header>(\S+)<\/header>/;
1823 my $header = $1 ;
1824 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1826 # update status in job queue to ...
1827 # ... 'processing', for jobs: 'reinstall', 'update'
1828 if (($header =~ /gosa_trigger_action_reinstall/)
1829 || ($header =~ /gosa_trigger_activate_new/)
1830 || ($header =~ /gosa_trigger_action_update/)) {
1831 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1832 my $dbres = $job_db->update_dbentry($sql_statement);
1833 }
1835 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1836 else {
1837 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1838 my $dbres = $job_db->update_dbentry($sql_statement);
1839 }
1842 # We don't want parallel processing
1843 last;
1844 }
1845 }
1846 }
1848 $watch_for_new_jobs_in_progress = 0;
1849 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1850 }
1851 }
1854 sub watch_for_new_messages {
1855 my ($kernel,$heap) = @_[KERNEL, HEAP];
1856 my @coll_user_msg; # collection list of outgoing messages
1858 # check messaging_db for new incoming messages with executable timestamp
1859 my $timestamp = &get_time();
1860 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1861 my $res = $messaging_db->exec_statement( $sql_statement );
1862 foreach my $hit (@{$res}) {
1864 # create outgoing messages
1865 my $message_to = @{$hit}[3];
1866 # translate message_to to plain login name
1867 my @message_to_l = split(/,/, $message_to);
1868 my %receiver_h;
1869 foreach my $receiver (@message_to_l) {
1870 if ($receiver =~ /^u_([\s\S]*)$/) {
1871 $receiver_h{$1} = 0;
1872 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1873 my $group_name = $1;
1874 # fetch all group members from ldap and add them to receiver hash
1875 my $ldap_handle = &get_ldap_handle();
1876 if (defined $ldap_handle) {
1877 my $mesg = $ldap_handle->search(
1878 base => $ldap_base,
1879 scope => 'sub',
1880 attrs => ['memberUid'],
1881 filter => "cn=$group_name",
1882 );
1883 if ($mesg->count) {
1884 my @entries = $mesg->entries;
1885 foreach my $entry (@entries) {
1886 my @receivers= $entry->get_value("memberUid");
1887 foreach my $receiver (@receivers) {
1888 $receiver_h{$receiver} = 0;
1889 }
1890 }
1891 }
1892 # translating errors ?
1893 if ($mesg->code) {
1894 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1895 }
1896 # ldap handle error ?
1897 } else {
1898 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1899 }
1900 } else {
1901 my $sbjct = &encode_base64(@{$hit}[1]);
1902 my $msg = &encode_base64(@{$hit}[7]);
1903 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1904 }
1905 }
1906 my @receiver_l = keys(%receiver_h);
1908 my $message_id = @{$hit}[0];
1910 #add each outgoing msg to messaging_db
1911 my $receiver;
1912 foreach $receiver (@receiver_l) {
1913 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1914 "VALUES ('".
1915 $message_id."', '". # id
1916 @{$hit}[1]."', '". # subject
1917 @{$hit}[2]."', '". # message_from
1918 $receiver."', '". # message_to
1919 "none"."', '". # flag
1920 "out"."', '". # direction
1921 @{$hit}[6]."', '". # delivery_time
1922 @{$hit}[7]."', '". # message
1923 $timestamp."'". # timestamp
1924 ")";
1925 &daemon_log("M DEBUG: $sql_statement", 1);
1926 my $res = $messaging_db->exec_statement($sql_statement);
1927 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1928 }
1930 # set incoming message to flag d=deliverd
1931 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1932 &daemon_log("M DEBUG: $sql_statement", 7);
1933 $res = $messaging_db->update_dbentry($sql_statement);
1934 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1935 }
1937 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1938 return;
1939 }
1941 sub watch_for_delivery_messages {
1942 my ($kernel, $heap) = @_[KERNEL, HEAP];
1944 # select outgoing messages
1945 my $timestamp= &get_time();
1946 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' AND delivery_time<$timestamp)";
1947 #&daemon_log("0 DEBUG: $sql", 7);
1948 my $res = $messaging_db->exec_statement( $sql_statement );
1950 # build out msg for each usr
1951 foreach my $hit (@{$res}) {
1952 my $receiver = @{$hit}[3];
1953 my $msg_id = @{$hit}[0];
1954 my $subject = @{$hit}[1];
1955 my $message = @{$hit}[7];
1957 # resolve usr -> host where usr is logged in
1958 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1959 #&daemon_log("0 DEBUG: $sql", 7);
1960 my $res = $login_users_db->exec_statement($sql);
1962 # receiver is logged in nowhere
1963 if (not ref(@$res[0]) eq "ARRAY") { next; }
1965 # receiver ist logged in at a client registered at local server
1966 my $send_succeed = 0;
1967 foreach my $hit (@$res) {
1968 my $receiver_host = @$hit[0];
1969 my $delivered2host = 0;
1970 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1972 # Looking for host in know_clients_db
1973 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1974 my $res = $known_clients_db->exec_statement($sql);
1976 # Host is known in known_clients_db
1977 if (ref(@$res[0]) eq "ARRAY") {
1978 my $receiver_key = @{@{$res}[0]}[2];
1979 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1980 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1981 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1982 if ($error == 0 ) {
1983 $send_succeed++ ;
1984 $delivered2host++ ;
1985 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1986 } else {
1987 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1988 }
1989 }
1991 # Message already send, do not need to do anything more, otherwise ...
1992 if ($delivered2host) { next;}
1994 # ...looking for host in foreign_clients_db
1995 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1996 $res = $foreign_clients_db->exec_statement($sql);
1998 # Host is known in foreign_clients_db
1999 if (ref(@$res[0]) eq "ARRAY") {
2000 my $registration_server = @{@{$res}[0]}[2];
2002 # Fetch encryption key for registration server
2003 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2004 my $res = $known_server_db->exec_statement($sql);
2005 if (ref(@$res[0]) eq "ARRAY") {
2006 my $registration_server_key = @{@{$res}[0]}[3];
2007 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2008 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2009 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2010 if ($error == 0 ) {
2011 $send_succeed++ ;
2012 $delivered2host++ ;
2013 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2014 } else {
2015 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2016 }
2018 } else {
2019 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2020 "registrated at server '$registration_server', ".
2021 "but no data available in known_server_db ", 1);
2022 }
2023 }
2025 if (not $delivered2host) {
2026 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2027 }
2028 }
2030 if ($send_succeed) {
2031 # set outgoing msg at db to deliverd
2032 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2033 my $res = $messaging_db->exec_statement($sql);
2034 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2035 } else {
2036 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2037 }
2038 }
2040 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2041 return;
2042 }
2045 sub watch_for_done_messages {
2046 my ($kernel,$heap) = @_[KERNEL, HEAP];
2048 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2049 #&daemon_log("0 DEBUG: $sql", 7);
2050 my $res = $messaging_db->exec_statement($sql);
2052 foreach my $hit (@{$res}) {
2053 my $msg_id = @{$hit}[0];
2055 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2056 #&daemon_log("0 DEBUG: $sql", 7);
2057 my $res = $messaging_db->exec_statement($sql);
2059 # not all usr msgs have been seen till now
2060 if ( ref(@$res[0]) eq "ARRAY") { next; }
2062 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2063 #&daemon_log("0 DEBUG: $sql", 7);
2064 $res = $messaging_db->exec_statement($sql);
2066 }
2068 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2069 return;
2070 }
2073 sub watch_for_old_known_clients {
2074 my ($kernel,$heap) = @_[KERNEL, HEAP];
2076 my $sql_statement = "SELECT * FROM $known_clients_tn";
2077 my $res = $known_clients_db->select_dbentry( $sql_statement );
2079 my $cur_time = int(&get_time());
2081 while ( my ($hit_num, $hit) = each %$res) {
2082 my $expired_timestamp = int($hit->{'timestamp'});
2083 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2084 my $dt = DateTime->new( year => $1,
2085 month => $2,
2086 day => $3,
2087 hour => $4,
2088 minute => $5,
2089 second => $6,
2090 );
2092 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2093 $expired_timestamp = $dt->ymd('').$dt->hms('');
2094 if ($cur_time > $expired_timestamp) {
2095 my $hostname = $hit->{'hostname'};
2096 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2097 my $del_res = $known_clients_db->exec_statement($del_sql);
2099 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2100 }
2102 }
2104 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2105 }
2108 sub watch_for_next_tasks {
2109 my ($kernel,$heap) = @_[KERNEL, HEAP];
2111 my $sql = "SELECT * FROM $incoming_tn";
2112 my $res = $incoming_db->select_dbentry($sql);
2114 while ( my ($hit_num, $hit) = each %$res) {
2115 my $headertag = $hit->{'headertag'};
2116 if ($headertag =~ /^answer_(\d+)/) {
2117 # do not start processing, this message is for a still running POE::Wheel
2118 next;
2119 }
2120 my $message_id = $hit->{'id'};
2121 my $session_id = $hit->{'sessionid'};
2122 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2123 $kernel->yield('next_task', $hit);
2125 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2126 my $res = $incoming_db->exec_statement($sql);
2127 }
2129 $kernel->delay_set('watch_for_next_tasks', 1);
2130 }
2133 sub get_ldap_handle {
2134 my ($session_id) = @_;
2135 my $heap;
2136 my $ldap_handle;
2138 if (not defined $session_id ) { $session_id = 0 };
2139 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2141 if ($session_id == 0) {
2142 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2143 $ldap_handle = Net::LDAP->new( $ldap_uri );
2144 if (defined $ldap_handle) {
2145 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2146 } else {
2147 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2148 }
2150 } else {
2151 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2152 if( defined $session_reference ) {
2153 $heap = $session_reference->get_heap();
2154 }
2156 if (not defined $heap) {
2157 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2158 return;
2159 }
2161 # TODO: This "if" is nonsense, because it doesn't prove that the
2162 # used handle is still valid - or if we've to reconnect...
2163 #if (not exists $heap->{ldap_handle}) {
2164 $ldap_handle = Net::LDAP->new( $ldap_uri );
2165 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2166 $heap->{ldap_handle} = $ldap_handle;
2167 #}
2168 }
2169 return $ldap_handle;
2170 }
2173 sub change_fai_state {
2174 my ($st, $targets, $session_id) = @_;
2175 $session_id = 0 if not defined $session_id;
2176 # Set FAI state to localboot
2177 my %mapActions= (
2178 reboot => '',
2179 update => 'softupdate',
2180 localboot => 'localboot',
2181 reinstall => 'install',
2182 rescan => '',
2183 wake => '',
2184 memcheck => 'memcheck',
2185 sysinfo => 'sysinfo',
2186 install => 'install',
2187 );
2189 # Return if this is unknown
2190 if (!exists $mapActions{ $st }){
2191 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2192 return;
2193 }
2195 my $state= $mapActions{ $st };
2197 my $ldap_handle = &get_ldap_handle($session_id);
2198 if( defined($ldap_handle) ) {
2200 # Build search filter for hosts
2201 my $search= "(&(objectClass=GOhard)";
2202 foreach (@{$targets}){
2203 $search.= "(macAddress=$_)";
2204 }
2205 $search.= ")";
2207 # If there's any host inside of the search string, procress them
2208 if (!($search =~ /macAddress/)){
2209 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2210 return;
2211 }
2213 # Perform search for Unit Tag
2214 my $mesg = $ldap_handle->search(
2215 base => $ldap_base,
2216 scope => 'sub',
2217 attrs => ['dn', 'FAIstate', 'objectClass'],
2218 filter => "$search"
2219 );
2221 if ($mesg->count) {
2222 my @entries = $mesg->entries;
2223 if (0 == @entries) {
2224 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2225 }
2227 foreach my $entry (@entries) {
2228 # Only modify entry if it is not set to '$state'
2229 if ($entry->get_value("FAIstate") ne "$state"){
2230 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2231 my $result;
2232 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2233 if (exists $tmp{'FAIobject'}){
2234 if ($state eq ''){
2235 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2236 } else {
2237 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2238 }
2239 } elsif ($state ne ''){
2240 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2241 }
2243 # Errors?
2244 if ($result->code){
2245 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2246 }
2247 } else {
2248 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2249 }
2250 }
2251 } else {
2252 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2253 }
2255 # if no ldap handle defined
2256 } else {
2257 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2258 }
2260 return;
2261 }
2264 sub change_goto_state {
2265 my ($st, $targets, $session_id) = @_;
2266 $session_id = 0 if not defined $session_id;
2268 # Switch on or off?
2269 my $state= $st eq 'active' ? 'active': 'locked';
2271 my $ldap_handle = &get_ldap_handle($session_id);
2272 if( defined($ldap_handle) ) {
2274 # Build search filter for hosts
2275 my $search= "(&(objectClass=GOhard)";
2276 foreach (@{$targets}){
2277 $search.= "(macAddress=$_)";
2278 }
2279 $search.= ")";
2281 # If there's any host inside of the search string, procress them
2282 if (!($search =~ /macAddress/)){
2283 return;
2284 }
2286 # Perform search for Unit Tag
2287 my $mesg = $ldap_handle->search(
2288 base => $ldap_base,
2289 scope => 'sub',
2290 attrs => ['dn', 'gotoMode'],
2291 filter => "$search"
2292 );
2294 if ($mesg->count) {
2295 my @entries = $mesg->entries;
2296 foreach my $entry (@entries) {
2298 # Only modify entry if it is not set to '$state'
2299 if ($entry->get_value("gotoMode") ne $state){
2301 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2302 my $result;
2303 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2305 # Errors?
2306 if ($result->code){
2307 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2308 }
2310 }
2311 }
2312 } else {
2313 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2314 }
2316 }
2317 }
2320 sub run_recreate_packages_db {
2321 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2322 my $session_id = $session->ID;
2323 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2324 $kernel->yield('create_fai_release_db', $fai_release_tn);
2325 $kernel->yield('create_fai_server_db', $fai_server_tn);
2326 return;
2327 }
2330 sub run_create_fai_server_db {
2331 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2332 my $session_id = $session->ID;
2333 my $task = POE::Wheel::Run->new(
2334 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2335 StdoutEvent => "session_run_result",
2336 StderrEvent => "session_run_debug",
2337 CloseEvent => "session_run_done",
2338 );
2340 $heap->{task}->{ $task->ID } = $task;
2341 return;
2342 }
2345 sub create_fai_server_db {
2346 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2347 my $result;
2349 if (not defined $session_id) { $session_id = 0; }
2350 my $ldap_handle = &get_ldap_handle();
2351 if(defined($ldap_handle)) {
2352 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2353 my $mesg= $ldap_handle->search(
2354 base => $ldap_base,
2355 scope => 'sub',
2356 attrs => ['FAIrepository', 'gosaUnitTag'],
2357 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2358 );
2359 if($mesg->{'resultCode'} == 0 &&
2360 $mesg->count != 0) {
2361 foreach my $entry (@{$mesg->{entries}}) {
2362 if($entry->exists('FAIrepository')) {
2363 # Add an entry for each Repository configured for server
2364 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2365 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2366 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2367 $result= $fai_server_db->add_dbentry( {
2368 table => $table_name,
2369 primkey => ['server', 'fai_release', 'tag'],
2370 server => $tmp_url,
2371 fai_release => $tmp_release,
2372 sections => $tmp_sections,
2373 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2374 } );
2375 }
2376 }
2377 }
2378 }
2379 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2381 # TODO: Find a way to post the 'create_packages_list_db' event
2382 if(not defined($dont_create_packages_list)) {
2383 &create_packages_list_db(undef, undef, $session_id);
2384 }
2385 }
2387 $ldap_handle->disconnect;
2388 return $result;
2389 }
2392 sub run_create_fai_release_db {
2393 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2394 my $session_id = $session->ID;
2395 my $task = POE::Wheel::Run->new(
2396 Program => sub { &create_fai_release_db($table_name, $session_id) },
2397 StdoutEvent => "session_run_result",
2398 StderrEvent => "session_run_debug",
2399 CloseEvent => "session_run_done",
2400 );
2402 $heap->{task}->{ $task->ID } = $task;
2403 return;
2404 }
2407 sub create_fai_release_db {
2408 my ($table_name, $session_id) = @_;
2409 my $result;
2411 # used for logging
2412 if (not defined $session_id) { $session_id = 0; }
2414 my $ldap_handle = &get_ldap_handle();
2415 if(defined($ldap_handle)) {
2416 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2417 my $mesg= $ldap_handle->search(
2418 base => $ldap_base,
2419 scope => 'sub',
2420 attrs => [],
2421 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2422 );
2423 if(($mesg->code == 0) && ($mesg->count != 0))
2424 {
2425 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2427 # Walk through all possible FAI container ou's
2428 my @sql_list;
2429 my $timestamp= &get_time();
2430 foreach my $ou (@{$mesg->{entries}}) {
2431 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2432 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2433 my @tmp_array=get_fai_release_entries($tmp_classes);
2434 if(@tmp_array) {
2435 foreach my $entry (@tmp_array) {
2436 if(defined($entry) && ref($entry) eq 'HASH') {
2437 my $sql=
2438 "INSERT INTO $table_name "
2439 ."(timestamp, fai_release, class, type, state) VALUES ("
2440 .$timestamp.","
2441 ."'".$entry->{'release'}."',"
2442 ."'".$entry->{'class'}."',"
2443 ."'".$entry->{'type'}."',"
2444 ."'".$entry->{'state'}."')";
2445 push @sql_list, $sql;
2446 }
2447 }
2448 }
2449 }
2450 }
2452 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2453 if(@sql_list) {
2454 unshift @sql_list, "VACUUM";
2455 unshift @sql_list, "DELETE FROM $table_name";
2456 $fai_release_db->exec_statementlist(\@sql_list);
2457 }
2458 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2459 } else {
2460 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2461 }
2462 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2463 }
2464 $ldap_handle->disconnect;
2465 return $result;
2466 }
2468 sub get_fai_types {
2469 my $tmp_classes = shift || return undef;
2470 my @result;
2472 foreach my $type(keys %{$tmp_classes}) {
2473 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2474 my $entry = {
2475 type => $type,
2476 state => $tmp_classes->{$type}[0],
2477 };
2478 push @result, $entry;
2479 }
2480 }
2482 return @result;
2483 }
2485 sub get_fai_state {
2486 my $result = "";
2487 my $tmp_classes = shift || return $result;
2489 foreach my $type(keys %{$tmp_classes}) {
2490 if(defined($tmp_classes->{$type}[0])) {
2491 $result = $tmp_classes->{$type}[0];
2493 # State is equal for all types in class
2494 last;
2495 }
2496 }
2498 return $result;
2499 }
2501 sub resolve_fai_classes {
2502 my ($fai_base, $ldap_handle, $session_id) = @_;
2503 if (not defined $session_id) { $session_id = 0; }
2504 my $result;
2505 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2506 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2507 my $fai_classes;
2509 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2510 my $mesg= $ldap_handle->search(
2511 base => $fai_base,
2512 scope => 'sub',
2513 attrs => ['cn','objectClass','FAIstate'],
2514 filter => $fai_filter,
2515 );
2516 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2518 if($mesg->{'resultCode'} == 0 &&
2519 $mesg->count != 0) {
2520 foreach my $entry (@{$mesg->{entries}}) {
2521 if($entry->exists('cn')) {
2522 my $tmp_dn= $entry->dn();
2523 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2524 - length($fai_base) - 1 );
2526 # Skip classname and ou dn parts for class
2527 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2529 # Skip classes without releases
2530 if((!defined($tmp_release)) || length($tmp_release)==0) {
2531 next;
2532 }
2534 my $tmp_cn= $entry->get_value('cn');
2535 my $tmp_state= $entry->get_value('FAIstate');
2537 my $tmp_type;
2538 # Get FAI type
2539 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2540 if(grep $_ eq $oclass, @possible_fai_classes) {
2541 $tmp_type= $oclass;
2542 last;
2543 }
2544 }
2546 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2547 # A Subrelease
2548 my @sub_releases = split(/,/, $tmp_release);
2550 # Walk through subreleases and build hash tree
2551 my $hash;
2552 while(my $tmp_sub_release = pop @sub_releases) {
2553 $hash .= "\{'$tmp_sub_release'\}->";
2554 }
2555 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2556 } else {
2557 # A branch, no subrelease
2558 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2559 }
2560 } elsif (!$entry->exists('cn')) {
2561 my $tmp_dn= $entry->dn();
2562 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2563 - length($fai_base) - 1 );
2564 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2566 # Skip classes without releases
2567 if((!defined($tmp_release)) || length($tmp_release)==0) {
2568 next;
2569 }
2571 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2572 # A Subrelease
2573 my @sub_releases= split(/,/, $tmp_release);
2575 # Walk through subreleases and build hash tree
2576 my $hash;
2577 while(my $tmp_sub_release = pop @sub_releases) {
2578 $hash .= "\{'$tmp_sub_release'\}->";
2579 }
2580 # Remove the last two characters
2581 chop($hash);
2582 chop($hash);
2584 eval('$fai_classes->'.$hash.'= {}');
2585 } else {
2586 # A branch, no subrelease
2587 if(!exists($fai_classes->{$tmp_release})) {
2588 $fai_classes->{$tmp_release} = {};
2589 }
2590 }
2591 }
2592 }
2594 # The hash is complete, now we can honor the copy-on-write based missing entries
2595 foreach my $release (keys %$fai_classes) {
2596 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2597 }
2598 }
2599 return $result;
2600 }
2602 sub apply_fai_inheritance {
2603 my $fai_classes = shift || return {};
2604 my $tmp_classes;
2606 # Get the classes from the branch
2607 foreach my $class (keys %{$fai_classes}) {
2608 # Skip subreleases
2609 if($class =~ /^ou=.*$/) {
2610 next;
2611 } else {
2612 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2613 }
2614 }
2616 # Apply to each subrelease
2617 foreach my $subrelease (keys %{$fai_classes}) {
2618 if($subrelease =~ /ou=/) {
2619 foreach my $tmp_class (keys %{$tmp_classes}) {
2620 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2621 $fai_classes->{$subrelease}->{$tmp_class} =
2622 deep_copy($tmp_classes->{$tmp_class});
2623 } else {
2624 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2625 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2626 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2627 deep_copy($tmp_classes->{$tmp_class}->{$type});
2628 }
2629 }
2630 }
2631 }
2632 }
2633 }
2635 # Find subreleases in deeper levels
2636 foreach my $subrelease (keys %{$fai_classes}) {
2637 if($subrelease =~ /ou=/) {
2638 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2639 if($subsubrelease =~ /ou=/) {
2640 apply_fai_inheritance($fai_classes->{$subrelease});
2641 }
2642 }
2643 }
2644 }
2646 return $fai_classes;
2647 }
2649 sub get_fai_release_entries {
2650 my $tmp_classes = shift || return;
2651 my $parent = shift || "";
2652 my @result = shift || ();
2654 foreach my $entry (keys %{$tmp_classes}) {
2655 if(defined($entry)) {
2656 if($entry =~ /^ou=.*$/) {
2657 my $release_name = $entry;
2658 $release_name =~ s/ou=//g;
2659 if(length($parent)>0) {
2660 $release_name = $parent."/".$release_name;
2661 }
2662 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2663 foreach my $bufentry(@bufentries) {
2664 push @result, $bufentry;
2665 }
2666 } else {
2667 my @types = get_fai_types($tmp_classes->{$entry});
2668 foreach my $type (@types) {
2669 push @result,
2670 {
2671 'class' => $entry,
2672 'type' => $type->{'type'},
2673 'release' => $parent,
2674 'state' => $type->{'state'},
2675 };
2676 }
2677 }
2678 }
2679 }
2681 return @result;
2682 }
2684 sub deep_copy {
2685 my $this = shift;
2686 if (not ref $this) {
2687 $this;
2688 } elsif (ref $this eq "ARRAY") {
2689 [map deep_copy($_), @$this];
2690 } elsif (ref $this eq "HASH") {
2691 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2692 } else { die "what type is $_?" }
2693 }
2696 sub session_run_result {
2697 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2698 $kernel->sig(CHLD => "child_reap");
2699 }
2701 sub session_run_debug {
2702 my $result = $_[ARG0];
2703 print STDERR "$result\n";
2704 }
2706 sub session_run_done {
2707 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2708 delete $heap->{task}->{$task_id};
2709 }
2712 sub create_sources_list {
2713 my $session_id = shift;
2714 my $ldap_handle = &main::get_ldap_handle;
2715 my $result="/tmp/gosa_si_tmp_sources_list";
2717 # Remove old file
2718 if(stat($result)) {
2719 unlink($result);
2720 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2721 }
2723 my $fh;
2724 open($fh, ">$result");
2725 if (not defined $fh) {
2726 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2727 return undef;
2728 }
2729 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2730 my $mesg=$ldap_handle->search(
2731 base => $main::ldap_server_dn,
2732 scope => 'base',
2733 attrs => 'FAIrepository',
2734 filter => 'objectClass=FAIrepositoryServer'
2735 );
2736 if($mesg->count) {
2737 foreach my $entry(@{$mesg->{'entries'}}) {
2738 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2739 my ($server, $tag, $release, $sections)= split /\|/, $value;
2740 my $line = "deb $server $release";
2741 $sections =~ s/,/ /g;
2742 $line.= " $sections";
2743 print $fh $line."\n";
2744 }
2745 }
2746 }
2747 } else {
2748 if (defined $main::ldap_server_dn){
2749 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2750 } else {
2751 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2752 }
2753 }
2754 close($fh);
2756 return $result;
2757 }
2760 sub run_create_packages_list_db {
2761 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2762 my $session_id = $session->ID;
2764 my $task = POE::Wheel::Run->new(
2765 Priority => +20,
2766 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2767 StdoutEvent => "session_run_result",
2768 StderrEvent => "session_run_debug",
2769 CloseEvent => "session_run_done",
2770 );
2771 $heap->{task}->{ $task->ID } = $task;
2772 }
2775 sub create_packages_list_db {
2776 my ($ldap_handle, $sources_file, $session_id) = @_;
2778 # it should not be possible to trigger a recreation of packages_list_db
2779 # while packages_list_db is under construction, so set flag packages_list_under_construction
2780 # which is tested befor recreation can be started
2781 if (-r $packages_list_under_construction) {
2782 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2783 return;
2784 } else {
2785 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2786 # set packages_list_under_construction to true
2787 system("touch $packages_list_under_construction");
2788 @packages_list_statements=();
2789 }
2791 if (not defined $session_id) { $session_id = 0; }
2792 if (not defined $ldap_handle) {
2793 $ldap_handle= &get_ldap_handle();
2795 if (not defined $ldap_handle) {
2796 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2797 unlink($packages_list_under_construction);
2798 return;
2799 }
2800 }
2801 if (not defined $sources_file) {
2802 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2803 $sources_file = &create_sources_list($session_id);
2804 }
2806 if (not defined $sources_file) {
2807 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2808 unlink($packages_list_under_construction);
2809 return;
2810 }
2812 my $line;
2814 open(CONFIG, "<$sources_file") or do {
2815 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2816 unlink($packages_list_under_construction);
2817 return;
2818 };
2820 # Read lines
2821 while ($line = <CONFIG>){
2822 # Unify
2823 chop($line);
2824 $line =~ s/^\s+//;
2825 $line =~ s/^\s+/ /;
2827 # Strip comments
2828 $line =~ s/#.*$//g;
2830 # Skip empty lines
2831 if ($line =~ /^\s*$/){
2832 next;
2833 }
2835 # Interpret deb line
2836 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2837 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2838 my $section;
2839 foreach $section (split(' ', $sections)){
2840 &parse_package_info( $baseurl, $dist, $section, $session_id );
2841 }
2842 }
2843 }
2845 close (CONFIG);
2847 if(keys(%repo_dirs)) {
2848 find(\&cleanup_and_extract, keys( %repo_dirs ));
2849 &main::strip_packages_list_statements();
2850 $packages_list_db->exec_statementlist(\@packages_list_statements);
2851 }
2852 unlink($packages_list_under_construction);
2853 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2854 return;
2855 }
2857 # This function should do some intensive task to minimize the db-traffic
2858 sub strip_packages_list_statements {
2859 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2860 my @new_statement_list=();
2861 my $hash;
2862 my $insert_hash;
2863 my $update_hash;
2864 my $delete_hash;
2865 my $known_packages_hash;
2866 my $local_timestamp=get_time();
2868 foreach my $existing_entry (@existing_entries) {
2869 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2870 }
2872 foreach my $statement (@packages_list_statements) {
2873 if($statement =~ /^INSERT/i) {
2874 # Assign the values from the insert statement
2875 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2876 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2877 if(exists($hash->{$distribution}->{$package}->{$version})) {
2878 # If section or description has changed, update the DB
2879 if(
2880 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2881 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2882 ) {
2883 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2884 } else {
2885 # package is already present in database. cache this knowledge for later use
2886 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2887 }
2888 } else {
2889 # Insert a non-existing entry to db
2890 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2891 }
2892 } elsif ($statement =~ /^UPDATE/i) {
2893 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2894 /^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;
2895 foreach my $distribution (keys %{$hash}) {
2896 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2897 # update the insertion hash to execute only one query per package (insert instead insert+update)
2898 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2899 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2900 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2901 my $section;
2902 my $description;
2903 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2904 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2905 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2906 }
2907 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2908 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2909 }
2910 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2911 }
2912 }
2913 }
2914 }
2915 }
2917 # Check for orphaned entries
2918 foreach my $existing_entry (@existing_entries) {
2919 my $distribution= @{$existing_entry}[0];
2920 my $package= @{$existing_entry}[1];
2921 my $version= @{$existing_entry}[2];
2922 my $section= @{$existing_entry}[3];
2924 if(
2925 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2926 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2927 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2928 ) {
2929 next;
2930 } else {
2931 # Insert entry to delete hash
2932 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2933 }
2934 }
2936 # unroll the insert hash
2937 foreach my $distribution (keys %{$insert_hash}) {
2938 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2939 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2940 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2941 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2942 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2943 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2944 ."'$local_timestamp')";
2945 }
2946 }
2947 }
2949 # unroll the update hash
2950 foreach my $distribution (keys %{$update_hash}) {
2951 foreach my $package (keys %{$update_hash->{$distribution}}) {
2952 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2953 my $set = "";
2954 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2955 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2956 }
2957 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2958 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2959 }
2960 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2961 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2962 }
2963 if(defined($set) and length($set) > 0) {
2964 $set .= "timestamp = '$local_timestamp'";
2965 } else {
2966 next;
2967 }
2968 push @new_statement_list,
2969 "UPDATE $main::packages_list_tn SET $set WHERE"
2970 ." distribution = '$distribution'"
2971 ." AND package = '$package'"
2972 ." AND version = '$version'";
2973 }
2974 }
2975 }
2977 # unroll the delete hash
2978 foreach my $distribution (keys %{$delete_hash}) {
2979 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2980 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2981 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2982 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2983 }
2984 }
2985 }
2987 unshift(@new_statement_list, "VACUUM");
2989 @packages_list_statements = @new_statement_list;
2990 }
2993 sub parse_package_info {
2994 my ($baseurl, $dist, $section, $session_id)= @_;
2995 my ($package);
2996 if (not defined $session_id) { $session_id = 0; }
2997 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2998 $repo_dirs{ "${repo_path}/pool" } = 1;
3000 foreach $package ("Packages.gz"){
3001 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3002 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3003 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3004 }
3006 }
3009 sub get_package {
3010 my ($url, $dest, $session_id)= @_;
3011 if (not defined $session_id) { $session_id = 0; }
3013 my $tpath = dirname($dest);
3014 -d "$tpath" || mkpath "$tpath";
3016 # This is ugly, but I've no time to take a look at "how it works in perl"
3017 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3018 system("gunzip -cd '$dest' > '$dest.in'");
3019 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3020 unlink($dest);
3021 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3022 } else {
3023 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3024 }
3025 return 0;
3026 }
3029 sub parse_package {
3030 my ($path, $dist, $srv_path, $session_id)= @_;
3031 if (not defined $session_id) { $session_id = 0;}
3032 my ($package, $version, $section, $description);
3033 my $PACKAGES;
3034 my $timestamp = &get_time();
3036 if(not stat("$path.in")) {
3037 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3038 return;
3039 }
3041 open($PACKAGES, "<$path.in");
3042 if(not defined($PACKAGES)) {
3043 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3044 return;
3045 }
3047 # Read lines
3048 while (<$PACKAGES>){
3049 my $line = $_;
3050 # Unify
3051 chop($line);
3053 # Use empty lines as a trigger
3054 if ($line =~ /^\s*$/){
3055 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3056 push(@packages_list_statements, $sql);
3057 $package = "none";
3058 $version = "none";
3059 $section = "none";
3060 $description = "none";
3061 next;
3062 }
3064 # Trigger for package name
3065 if ($line =~ /^Package:\s/){
3066 ($package)= ($line =~ /^Package: (.*)$/);
3067 next;
3068 }
3070 # Trigger for version
3071 if ($line =~ /^Version:\s/){
3072 ($version)= ($line =~ /^Version: (.*)$/);
3073 next;
3074 }
3076 # Trigger for description
3077 if ($line =~ /^Description:\s/){
3078 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3079 next;
3080 }
3082 # Trigger for section
3083 if ($line =~ /^Section:\s/){
3084 ($section)= ($line =~ /^Section: (.*)$/);
3085 next;
3086 }
3088 # Trigger for filename
3089 if ($line =~ /^Filename:\s/){
3090 my ($filename) = ($line =~ /^Filename: (.*)$/);
3091 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3092 next;
3093 }
3094 }
3096 close( $PACKAGES );
3097 unlink( "$path.in" );
3098 }
3101 sub store_fileinfo {
3102 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3104 my %fileinfo = (
3105 'package' => $package,
3106 'dist' => $dist,
3107 'version' => $vers,
3108 );
3110 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3111 }
3114 sub cleanup_and_extract {
3115 my $fileinfo = $repo_files{ $File::Find::name };
3117 if( defined $fileinfo ) {
3118 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3119 my $sql;
3120 my $package = $fileinfo->{ 'package' };
3121 my $newver = $fileinfo->{ 'version' };
3123 mkpath($dir);
3124 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3126 if( -f "$dir/DEBIAN/templates" ) {
3128 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3130 my $tmpl= ""; {
3131 local $/=undef;
3132 open FILE, "$dir/DEBIAN/templates";
3133 $tmpl = &encode_base64(<FILE>);
3134 close FILE;
3135 }
3136 rmtree("$dir/DEBIAN/templates");
3138 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3139 push @packages_list_statements, $sql;
3140 }
3141 }
3143 return;
3144 }
3147 sub register_at_foreign_servers {
3148 my ($kernel) = $_[KERNEL];
3150 # hole alle bekannten server aus known_server_db
3151 my $server_sql = "SELECT * FROM $known_server_tn";
3152 my $server_res = $known_server_db->exec_statement($server_sql);
3154 # no entries in known_server_db
3155 if (not ref(@$server_res[0]) eq "ARRAY") {
3156 # TODO
3157 }
3159 # detect already connected clients
3160 my $client_sql = "SELECT * FROM $known_clients_tn";
3161 my $client_res = $known_clients_db->exec_statement($client_sql);
3163 # send my server details to all other gosa-si-server within the network
3164 foreach my $hit (@$server_res) {
3165 my $hostname = @$hit[0];
3166 my $hostkey = &create_passwd;
3168 # add already connected clients to registration message
3169 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3170 &add_content2xml_hash($myhash, 'key', $hostkey);
3171 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3173 # add locally loaded gosa-si modules to registration message
3174 my $loaded_modules = {};
3175 while (my ($package, $pck_info) = each %$known_modules) {
3176 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3177 foreach my $act_module (keys(%{@$pck_info[2]})) {
3178 $loaded_modules->{$act_module} = "";
3179 }
3180 }
3182 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3184 # add macaddress to registration message
3185 my ($host_ip, $host_port) = split(/:/, $hostname);
3186 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3187 my $network_interface= &get_interface_for_ip($local_ip);
3188 my $host_mac = &get_mac_for_interface($network_interface);
3189 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3191 # build registration message and send it
3192 my $foreign_server_msg = &create_xml_string($myhash);
3193 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3194 }
3196 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3197 return;
3198 }
3201 #==== MAIN = main ==============================================================
3202 # parse commandline options
3203 Getopt::Long::Configure( "bundling" );
3204 GetOptions("h|help" => \&usage,
3205 "c|config=s" => \$cfg_file,
3206 "f|foreground" => \$foreground,
3207 "v|verbose+" => \$verbose,
3208 "no-arp+" => \$no_arp,
3209 );
3211 # Prepare UID / GID as daemon_log may need it quite early
3212 $root_uid = getpwnam('root');
3213 $adm_gid = getgrnam('adm');
3215 # read and set config parameters
3216 &check_cmdline_param ;
3217 &read_configfile($cfg_file, %cfg_defaults);
3218 &check_pid;
3220 $SIG{CHLD} = 'IGNORE';
3222 # forward error messages to logfile
3223 if( ! $foreground ) {
3224 open( STDIN, '+>/dev/null' );
3225 open( STDOUT, '+>&STDIN' );
3226 open( STDERR, '+>&STDIN' );
3227 }
3229 # Just fork, if we are not in foreground mode
3230 if( ! $foreground ) {
3231 chdir '/' or die "Can't chdir to /: $!";
3232 $pid = fork;
3233 setsid or die "Can't start a new session: $!";
3234 umask 0;
3235 } else {
3236 $pid = $$;
3237 }
3239 # Do something useful - put our PID into the pid_file
3240 if( 0 != $pid ) {
3241 open( LOCK_FILE, ">$pid_file" );
3242 print LOCK_FILE "$pid\n";
3243 close( LOCK_FILE );
3244 if( !$foreground ) {
3245 exit( 0 )
3246 };
3247 }
3249 # parse head url and revision from svn
3250 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3251 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3252 $server_headURL = defined $1 ? $1 : 'unknown' ;
3253 $server_revision = defined $2 ? $2 : 'unknown' ;
3254 if ($server_headURL =~ /\/tag\// ||
3255 $server_headURL =~ /\/branches\// ) {
3256 $server_status = "stable";
3257 } else {
3258 $server_status = "developmental" ;
3259 }
3261 # Prepare log file and set permissons
3262 open(FH, ">>$log_file");
3263 close FH;
3264 chmod(0440, $log_file);
3265 chown($root_uid, $adm_gid, $log_file);
3266 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3268 daemon_log(" ", 1);
3269 daemon_log("$0 started!", 1);
3270 daemon_log("status: $server_status", 1);
3271 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3273 {
3274 no strict "refs";
3276 if ($db_module eq "DBmysql") {
3277 # connect to incoming_db
3278 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3280 # connect to gosa-si job queue
3281 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3283 # connect to known_clients_db
3284 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3286 # connect to foreign_clients_db
3287 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3289 # connect to known_server_db
3290 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3292 # connect to login_usr_db
3293 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3295 # connect to fai_server_db
3296 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3298 # connect to fai_release_db
3299 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3301 # connect to packages_list_db
3302 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3304 # connect to messaging_db
3305 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3307 } elsif ($db_module eq "DBsqlite") {
3308 # connect to incoming_db
3309 unlink($incoming_file_name);
3310 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3312 # connect to gosa-si job queue
3313 unlink($job_queue_file_name); ## just for debugging
3314 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3315 chmod(0640, $job_queue_file_name);
3316 chown($root_uid, $adm_gid, $job_queue_file_name);
3318 # connect to known_clients_db
3319 unlink($known_clients_file_name); ## just for debugging
3320 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3321 chmod(0640, $known_clients_file_name);
3322 chown($root_uid, $adm_gid, $known_clients_file_name);
3324 # connect to foreign_clients_db
3325 unlink($foreign_clients_file_name);
3326 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3327 chmod(0640, $foreign_clients_file_name);
3328 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3330 # connect to known_server_db
3331 unlink($known_server_file_name);
3332 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3333 chmod(0640, $known_server_file_name);
3334 chown($root_uid, $adm_gid, $known_server_file_name);
3336 # connect to login_usr_db
3337 unlink($login_users_file_name);
3338 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3339 chmod(0640, $login_users_file_name);
3340 chown($root_uid, $adm_gid, $login_users_file_name);
3342 # connect to fai_server_db
3343 unlink($fai_server_file_name);
3344 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3345 chmod(0640, $fai_server_file_name);
3346 chown($root_uid, $adm_gid, $fai_server_file_name);
3348 # connect to fai_release_db
3349 unlink($fai_release_file_name);
3350 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3351 chmod(0640, $fai_release_file_name);
3352 chown($root_uid, $adm_gid, $fai_release_file_name);
3354 # connect to packages_list_db
3355 #unlink($packages_list_file_name);
3356 unlink($packages_list_under_construction);
3357 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3358 chmod(0640, $packages_list_file_name);
3359 chown($root_uid, $adm_gid, $packages_list_file_name);
3361 # connect to messaging_db
3362 unlink($messaging_file_name);
3363 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3364 chmod(0640, $messaging_file_name);
3365 chown($root_uid, $adm_gid, $messaging_file_name);
3366 }
3367 }
3370 # Creating tables
3371 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3372 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3373 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3374 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3375 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3376 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3377 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3378 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3379 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3380 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3382 # create xml object used for en/decrypting
3383 $xml = new XML::Simple();
3386 # foreign servers
3387 my @foreign_server_list;
3389 # add foreign server from cfg file
3390 if ($foreign_server_string ne "") {
3391 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3392 foreach my $foreign_server (@cfg_foreign_server_list) {
3393 push(@foreign_server_list, $foreign_server);
3394 }
3396 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3397 }
3399 # Perform a DNS lookup for server registration if flag is true
3400 if ($dns_lookup eq "true") {
3401 # Add foreign server from dns
3402 my @tmp_servers;
3403 if (not $server_domain) {
3404 # Try our DNS Searchlist
3405 for my $domain(get_dns_domains()) {
3406 chomp($domain);
3407 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3408 if(@$tmp_domains) {
3409 for my $tmp_server(@$tmp_domains) {
3410 push @tmp_servers, $tmp_server;
3411 }
3412 }
3413 }
3414 if(@tmp_servers && length(@tmp_servers)==0) {
3415 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3416 }
3417 } else {
3418 @tmp_servers = &get_server_addresses($server_domain);
3419 if( 0 == @tmp_servers ) {
3420 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3421 }
3422 }
3424 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3426 foreach my $server (@tmp_servers) {
3427 unshift(@foreign_server_list, $server);
3428 }
3429 } else {
3430 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3431 }
3434 # eliminate duplicate entries
3435 @foreign_server_list = &del_doubles(@foreign_server_list);
3436 my $all_foreign_server = join(", ", @foreign_server_list);
3437 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3439 # add all found foreign servers to known_server
3440 my $cur_timestamp = &get_time();
3441 foreach my $foreign_server (@foreign_server_list) {
3443 # do not add myself to known_server_db
3444 if (&is_local($foreign_server)) { next; }
3445 ######################################
3447 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3448 primkey=>['hostname'],
3449 hostname=>$foreign_server,
3450 macaddress=>"",
3451 status=>'not_yet_registered',
3452 hostkey=>"none",
3453 loaded_modules => "none",
3454 timestamp=>$cur_timestamp,
3455 } );
3456 }
3459 # Import all modules
3460 &import_modules;
3462 # Check wether all modules are gosa-si valid passwd check
3463 &password_check;
3465 # Prepare for using Opsi
3466 if ($opsi_enabled eq "true") {
3467 use JSON::RPC::Client;
3468 use XML::Quote qw(:all);
3469 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3470 $opsi_client = new JSON::RPC::Client;
3471 }
3474 POE::Component::Server::TCP->new(
3475 Alias => "TCP_SERVER",
3476 Port => $server_port,
3477 ClientInput => sub {
3478 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3479 my $session_id = $session->ID;
3480 my $remote_ip = $heap->{'remote_ip'};
3481 push(@msgs_to_decrypt, $input);
3482 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3483 $kernel->yield("msg_to_decrypt");
3484 },
3485 InlineStates => {
3486 msg_to_decrypt => \&msg_to_decrypt,
3487 next_task => \&next_task,
3488 task_result => \&handle_task_result,
3489 task_done => \&handle_task_done,
3490 task_debug => \&handle_task_debug,
3491 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3492 }
3493 );
3495 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3497 # create session for repeatedly checking the job queue for jobs
3498 POE::Session->create(
3499 inline_states => {
3500 _start => \&session_start,
3501 register_at_foreign_servers => \®ister_at_foreign_servers,
3502 sig_handler => \&sig_handler,
3503 next_task => \&next_task,
3504 task_result => \&handle_task_result,
3505 task_done => \&handle_task_done,
3506 task_debug => \&handle_task_debug,
3507 watch_for_next_tasks => \&watch_for_next_tasks,
3508 watch_for_new_messages => \&watch_for_new_messages,
3509 watch_for_delivery_messages => \&watch_for_delivery_messages,
3510 watch_for_done_messages => \&watch_for_done_messages,
3511 watch_for_new_jobs => \&watch_for_new_jobs,
3512 watch_for_modified_jobs => \&watch_for_modified_jobs,
3513 watch_for_done_jobs => \&watch_for_done_jobs,
3514 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3515 watch_for_old_known_clients => \&watch_for_old_known_clients,
3516 create_packages_list_db => \&run_create_packages_list_db,
3517 create_fai_server_db => \&run_create_fai_server_db,
3518 create_fai_release_db => \&run_create_fai_release_db,
3519 recreate_packages_db => \&run_recreate_packages_db,
3520 session_run_result => \&session_run_result,
3521 session_run_debug => \&session_run_debug,
3522 session_run_done => \&session_run_done,
3523 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3524 }
3525 );
3528 POE::Kernel->run();
3529 exit;