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 }
513 sub clean_shutdown
514 {
515 unlink($pid_file) if (-w $pid_file);
516 unlink($packages_list_under_construction) if (-w $packages_list_under_construction);
517 }
519 sub sig_int_or_term_handler
520 {
521 my ($signal) = @_;
522 daemon_log("Got SIG${signal} - shutting down gosa-si-server", 1);
523 clean_shutdown();
524 POE::Kernel->signal('gosa-si_server_session', 'KILL');
525 POE::Kernel->signal('TCP_SERVER', 'KILL');
526 return 1;
527 }
529 sub sig_warn_handler
530 {
531 my @loc = caller(0);
532 daemon_log( "SIGWARN line " . $loc[2] . ": " . $_[0], 1 );
533 return 1;
534 }
536 $SIG{'INT'} = \&sig_int_or_term_handler;
537 $SIG{'TERM'} = \&sig_int_or_term_handler;
538 $SIG{'__WARN__'} = \&sig_warn_handler;
539 $SIG{'USR1'} = 'IGNORE';
541 sub check_key_and_xml_validity {
542 my ($crypted_msg, $module_key, $session_id) = @_;
543 my $msg;
544 my $msg_hash;
545 my $error_string;
546 eval{
547 $msg = &decrypt_msg($crypted_msg, $module_key);
549 if ($msg =~ /<xml>/i){
550 $msg =~ s/\s+/ /g; # just for better daemon_log
551 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
552 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
554 ##############
555 # check header
556 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
557 my $header_l = $msg_hash->{'header'};
558 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
559 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
560 my $header = @{$header_l}[0];
561 if( 0 == length $header) { die 'empty string in header tag'; }
563 ##############
564 # check source
565 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
566 my $source_l = $msg_hash->{'source'};
567 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
568 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
569 my $source = @{$source_l}[0];
570 if( 0 == length $source) { die 'source error'; }
572 ##############
573 # check target
574 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
575 my $target_l = $msg_hash->{'target'};
576 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
577 }
578 };
579 if($@) {
580 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
581 $msg = undef;
582 $msg_hash = undef;
583 }
585 return ($msg, $msg_hash);
586 }
589 sub check_outgoing_xml_validity {
590 my ($msg, $session_id) = @_;
592 my $msg_hash;
593 eval{
594 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
596 ##############
597 # check header
598 my $header_l = $msg_hash->{'header'};
599 if( 1 != @{$header_l} ) {
600 die 'no or more than one headers specified';
601 }
602 my $header = @{$header_l}[0];
603 if( 0 == length $header) {
604 die 'header has length 0';
605 }
607 ##############
608 # check source
609 my $source_l = $msg_hash->{'source'};
610 if( 1 != @{$source_l} ) {
611 die 'no or more than 1 sources specified';
612 }
613 my $source = @{$source_l}[0];
614 if( 0 == length $source) {
615 die 'source has length 0';
616 }
618 # Check if source contains hostname instead of ip address
619 if($source =~ /^[a-z][a-z0-9\.]+:\d+$/i) {
620 my ($hostname,$port) = split(/:/, $source);
621 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
622 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
623 # Write ip address to $source variable
624 $source = "$ip_address:$port";
625 }
626 }
627 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
628 $source =~ /^GOSA$/i) {
629 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
630 }
632 ##############
633 # check target
634 my $target_l = $msg_hash->{'target'};
635 if( 0 == @{$target_l} ) {
636 die "no targets specified";
637 }
638 foreach my $target (@$target_l) {
639 if( 0 == length $target) {
640 die "target has length 0";
641 }
642 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
643 $target =~ /^GOSA$/i ||
644 $target =~ /^\*$/ ||
645 $target =~ /KNOWN_SERVER/i ||
646 $target =~ /JOBDB/i ||
647 $target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ){
648 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
649 }
650 }
651 };
652 if($@) {
653 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
654 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
655 $msg_hash = undef;
656 }
658 return ($msg_hash);
659 }
662 sub input_from_known_server {
663 my ($input, $remote_ip, $session_id) = @_ ;
664 my ($msg, $msg_hash, $module);
666 my $sql_statement= "SELECT * FROM known_server";
667 my $query_res = $known_server_db->select_dbentry( $sql_statement );
669 while( my ($hit_num, $hit) = each %{ $query_res } ) {
670 my $host_name = $hit->{hostname};
671 if( not $host_name =~ "^$remote_ip") {
672 next;
673 }
674 my $host_key = $hit->{hostkey};
675 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
676 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
678 # check if module can open msg envelope with module key
679 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
680 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
681 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
682 daemon_log("$@", 8);
683 next;
684 }
685 else {
686 $msg = $tmp_msg;
687 $msg_hash = $tmp_msg_hash;
688 $module = "ServerPackages";
689 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
690 last;
691 }
692 }
694 if( (!$msg) || (!$msg_hash) || (!$module) ) {
695 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
696 }
698 return ($msg, $msg_hash, $module);
699 }
702 sub input_from_known_client {
703 my ($input, $remote_ip, $session_id) = @_ ;
704 my ($msg, $msg_hash, $module);
706 my $sql_statement= "SELECT * FROM known_clients";
707 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
708 while( my ($hit_num, $hit) = each %{ $query_res } ) {
709 my $host_name = $hit->{hostname};
710 if( not $host_name =~ /^$remote_ip:\d*$/) {
711 next;
712 }
713 my $host_key = $hit->{hostkey};
714 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
715 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
717 # check if module can open msg envelope with module key
718 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
720 if( (!$msg) || (!$msg_hash) ) {
721 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
722 &daemon_log("$@", 8);
723 next;
724 }
725 else {
726 $module = "ClientPackages";
727 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
728 last;
729 }
730 }
732 if( (!$msg) || (!$msg_hash) || (!$module) ) {
733 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
734 }
736 return ($msg, $msg_hash, $module);
737 }
740 sub input_from_unknown_host {
741 no strict "refs";
742 my ($input, $session_id) = @_ ;
743 my ($msg, $msg_hash, $module);
744 my $error_string;
746 my %act_modules = %$known_modules;
748 while( my ($mod, $info) = each(%act_modules)) {
750 # check a key exists for this module
751 my $module_key = ${$mod."_key"};
752 if( not defined $module_key ) {
753 if( $mod eq 'ArpHandler' ) {
754 next;
755 }
756 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
757 next;
758 }
759 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
761 # check if module can open msg envelope with module key
762 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
763 if( (not defined $msg) || (not defined $msg_hash) ) {
764 next;
765 } else {
766 $module = $mod;
767 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
768 last;
769 }
770 }
772 if( (!$msg) || (!$msg_hash) || (!$module)) {
773 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
774 }
776 return ($msg, $msg_hash, $module);
777 }
780 sub create_ciphering {
781 my ($passwd) = @_;
782 if((!defined($passwd)) || length($passwd)==0) {
783 $passwd = "";
784 }
785 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
786 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
787 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
788 $my_cipher->set_iv($iv);
789 return $my_cipher;
790 }
793 sub encrypt_msg {
794 my ($msg, $key) = @_;
795 my $my_cipher = &create_ciphering($key);
796 my $len;
797 {
798 use bytes;
799 $len= 16-length($msg)%16;
800 }
801 $msg = "\0"x($len).$msg;
802 $msg = $my_cipher->encrypt($msg);
803 chomp($msg = &encode_base64($msg));
804 # there are no newlines allowed inside msg
805 $msg=~ s/\n//g;
806 return $msg;
807 }
810 sub decrypt_msg {
812 my ($msg, $key) = @_ ;
813 $msg = &decode_base64($msg);
814 my $my_cipher = &create_ciphering($key);
815 $msg = $my_cipher->decrypt($msg);
816 $msg =~ s/\0*//g;
817 return $msg;
818 }
821 sub get_encrypt_key {
822 my ($target) = @_ ;
823 my $encrypt_key;
824 my $error = 0;
826 # target can be in known_server
827 if( not defined $encrypt_key ) {
828 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
829 my $query_res = $known_server_db->select_dbentry( $sql_statement );
830 while( my ($hit_num, $hit) = each %{ $query_res } ) {
831 my $host_name = $hit->{hostname};
832 if( $host_name ne $target ) {
833 next;
834 }
835 $encrypt_key = $hit->{hostkey};
836 last;
837 }
838 }
840 # target can be in known_client
841 if( not defined $encrypt_key ) {
842 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
843 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
844 while( my ($hit_num, $hit) = each %{ $query_res } ) {
845 my $host_name = $hit->{hostname};
846 if( $host_name ne $target ) {
847 next;
848 }
849 $encrypt_key = $hit->{hostkey};
850 last;
851 }
852 }
854 return $encrypt_key;
855 }
858 #=== FUNCTION ================================================================
859 # NAME: open_socket
860 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
861 # [PeerPort] string necessary if port not appended by PeerAddr
862 # RETURNS: socket IO::Socket::INET
863 # DESCRIPTION: open a socket to PeerAddr
864 #===============================================================================
865 sub open_socket {
866 my ($PeerAddr, $PeerPort) = @_ ;
867 if(defined($PeerPort)){
868 $PeerAddr = $PeerAddr.":".$PeerPort;
869 }
870 my $socket;
871 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
872 Porto => "tcp",
873 Type => SOCK_STREAM,
874 Timeout => 5,
875 );
876 if(not defined $socket) {
877 return;
878 }
879 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
880 return $socket;
881 }
884 sub send_msg_to_target {
885 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
886 my $error = 0;
887 my $header;
888 my $timestamp = &get_time();
889 my $new_status;
890 my $act_status;
891 my ($sql_statement, $res);
893 if( $msg_header ) {
894 $header = "'$msg_header'-";
895 } else {
896 $header = "";
897 }
899 # Patch the source ip
900 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
901 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
902 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
903 }
905 # encrypt xml msg
906 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
908 # opensocket
909 my $socket = &open_socket($address);
910 if( !$socket ) {
911 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
912 $error++;
913 }
915 if( $error == 0 ) {
916 # send xml msg
917 print $socket $crypted_msg."\n";
919 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
920 daemon_log("$session_id DEBUG: message:\n$msg", 9);
922 }
924 # close socket in any case
925 if( $socket ) {
926 close $socket;
927 }
929 if( $error > 0 ) { $new_status = "down"; }
930 else { $new_status = $msg_header; }
933 # known_clients
934 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
935 $res = $known_clients_db->select_dbentry($sql_statement);
936 if( keys(%$res) == 1) {
937 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
938 if ($act_status eq "down" && $new_status eq "down") {
939 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
940 $res = $known_clients_db->del_dbentry($sql_statement);
941 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
942 } else {
943 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
944 $res = $known_clients_db->update_dbentry($sql_statement);
945 if($new_status eq "down"){
946 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
947 } else {
948 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
949 }
950 }
951 }
953 # known_server
954 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
955 $res = $known_server_db->select_dbentry($sql_statement);
956 if( keys(%$res) == 1) {
957 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
958 if ($act_status eq "down" && $new_status eq "down") {
959 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
960 $res = $known_server_db->del_dbentry($sql_statement);
961 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
963 # Remove the registered clients of the server as well
964 $sql_statement = "DELETE FROM foreign_clients WHERE regserver='$address'";
965 $res = $foreign_clients_db->del_dbentry($sql_statement);
966 }
967 else {
968 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
969 $res = $known_server_db->update_dbentry($sql_statement);
970 if($new_status eq "down"){
971 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
972 } else {
973 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
974 }
975 }
976 }
977 return $error;
978 }
981 sub update_jobdb_status_for_send_msgs {
982 my ($session_id, $answer, $error) = @_;
983 &daemon_log("$session_id DEBUG: try to update job status", 7);
984 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
985 my $jobdb_id = $1;
987 $answer =~ /<header>(.*)<\/header>/;
988 my $job_header = $1;
990 $answer =~ /<target>(.*)<\/target>/;
991 my $job_target = $1;
993 # Sending msg failed
994 if( $error ) {
996 # Set jobs to done, jobs do not need to deliver their message in any case
997 if (($job_header eq "trigger_action_localboot")
998 ||($job_header eq "trigger_action_lock")
999 ||($job_header eq "trigger_action_halt")
1000 ) {
1001 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1002 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1003 my $res = $job_db->update_dbentry($sql_statement);
1005 # Reactivate jobs, jobs need to deliver their message
1006 } elsif (($job_header eq "trigger_action_activate")
1007 ||($job_header eq "trigger_action_update")
1008 ||($job_header eq "trigger_action_reinstall")
1009 ||($job_header eq "trigger_activate_new")
1010 ) {
1011 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1013 # For all other messages
1014 } else {
1015 my $sql_statement = "UPDATE $job_queue_tn ".
1016 "SET status='error', result='can not deliver msg, please consult log file' ".
1017 "WHERE id=$jobdb_id";
1018 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1019 my $res = $job_db->update_dbentry($sql_statement);
1020 }
1022 # Sending msg was successful
1023 } else {
1024 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1025 # jobs reinstall, update, inst_update do themself setting to done
1026 if (($job_header eq "trigger_action_localboot")
1027 ||($job_header eq "trigger_action_lock")
1028 ||($job_header eq "trigger_action_activate")
1029 ||($job_header eq "trigger_action_halt")
1030 ||($job_header eq "trigger_action_reboot")
1031 ||($job_header eq "trigger_action_wake")
1032 ||($job_header eq "trigger_wake")
1033 ) {
1035 my $sql_statement = "UPDATE $job_queue_tn ".
1036 "SET status='done' ".
1037 "WHERE id=$jobdb_id AND status='processed'";
1038 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1039 my $res = $job_db->update_dbentry($sql_statement);
1040 } else {
1041 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 7);
1042 }
1043 }
1044 } else {
1045 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag: $answer", 7);
1046 }
1047 }
1049 sub reactivate_job_with_delay {
1050 my ($session_id, $target, $header, $delay) = @_ ;
1051 # 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
1053 if (not defined $delay) { $delay = 30 } ;
1054 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1056 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1057 my $res = $job_db->update_dbentry($sql);
1058 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1059 "cause client '$target' is currently not available", 5);
1060 daemon_log("$session_id $sql", 7);
1061 return;
1062 }
1065 sub msg_to_decrypt {
1066 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1067 my $session_id = $session->ID;
1068 my ($msg, $msg_hash, $module);
1069 my $error = 0;
1071 # fetch new msg out of @msgs_to_decrypt
1072 my $tmp_next_msg = shift @msgs_to_decrypt;
1073 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1075 # msg is from a new client or gosa
1076 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1078 # msg is from a gosa-si-server
1079 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1080 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1081 }
1082 # msg is from a gosa-si-client
1083 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1084 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1085 }
1086 # an error occurred
1087 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1088 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1089 # could not understand a msg from its server the client cause a re-registering process
1090 my $remote_ip = $heap->{'remote_ip'};
1091 my $remote_port = $heap->{'remote_port'};
1092 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1093 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1095 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1096 "' to cause a re-registering of the client if necessary", 3);
1097 $error++;
1098 }
1101 my $header;
1102 my $target;
1103 my $source;
1104 my $done = 0;
1105 my $sql;
1106 my $res;
1108 # check whether this message should be processed here
1109 if ($error == 0) {
1110 $header = @{$msg_hash->{'header'}}[0];
1111 $target = @{$msg_hash->{'target'}}[0];
1112 $source = @{$msg_hash->{'source'}}[0];
1113 my $not_found_in_known_clients_db = 0;
1114 my $not_found_in_known_server_db = 0;
1115 my $not_found_in_foreign_clients_db = 0;
1116 my $local_address;
1117 my $local_mac;
1118 my ($target_ip, $target_port) = split(':', $target);
1120 # Determine the local ip address if target is an ip address
1121 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1122 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1123 } else {
1124 $local_address = $server_address;
1125 }
1127 # Determine the local mac address if target is a mac address
1128 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) {
1129 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1130 my $network_interface= &get_interface_for_ip($loc_ip);
1131 $local_mac = &get_mac_for_interface($network_interface);
1132 } else {
1133 $local_mac = $server_mac_address;
1134 }
1136 # target and source is equal to GOSA -> process here
1137 if (not $done) {
1138 if ($target eq "GOSA" && $source eq "GOSA") {
1139 $done = 1;
1140 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1141 }
1142 }
1144 # target is own address without forward_to_gosa-tag -> process here
1145 if (not $done) {
1146 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1147 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1148 $done = 1;
1149 if ($source eq "GOSA") {
1150 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1151 }
1152 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1153 }
1154 }
1156 # target is a client address in known_clients -> process here
1157 if (not $done) {
1158 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1159 $res = $known_clients_db->select_dbentry($sql);
1160 if (keys(%$res) > 0) {
1161 $done = 1;
1162 my $hostname = $res->{1}->{'hostname'};
1163 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1164 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1165 if ($source eq "GOSA") {
1166 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1167 }
1168 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1170 } else {
1171 $not_found_in_known_clients_db = 1;
1172 }
1173 }
1175 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1176 if (not $done) {
1177 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1178 my $gosa_at;
1179 my $gosa_session_id;
1180 if (($target eq $local_address) && (defined $forward_to_gosa)){
1181 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1182 if ($gosa_at ne $local_address) {
1183 $done = 1;
1184 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1185 }
1186 }
1187 }
1189 # if message should be processed here -> add message to incoming_db
1190 if ($done) {
1191 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1192 # so gosa-si-server knows how to process this kind of messages
1193 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1194 $module = "GosaPackages";
1195 }
1197 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1198 primkey=>[],
1199 headertag=>$header,
1200 targettag=>$target,
1201 xmlmessage=>&encode_base64($msg),
1202 timestamp=>&get_time,
1203 module=>$module,
1204 sessionid=>$session_id,
1205 } );
1207 }
1209 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1210 if (not $done) {
1211 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1212 my $gosa_at;
1213 my $gosa_session_id;
1214 if (($target eq $local_address) && (defined $forward_to_gosa)){
1215 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1216 if ($gosa_at eq $local_address) {
1217 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1218 if( defined $session_reference ) {
1219 $heap = $session_reference->get_heap();
1220 }
1221 if(exists $heap->{'client'}) {
1222 $msg = &encrypt_msg($msg, $GosaPackages_key);
1223 $heap->{'client'}->put($msg);
1224 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1225 }
1226 $done = 1;
1227 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1228 }
1229 }
1231 }
1233 # target is a client address in foreign_clients -> forward to registration server
1234 if (not $done) {
1235 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1236 $res = $foreign_clients_db->select_dbentry($sql);
1237 if (keys(%$res) > 0) {
1238 my $hostname = $res->{1}->{'hostname'};
1239 my ($host_ip, $host_port) = split(/:/, $hostname);
1240 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1241 my $regserver = $res->{1}->{'regserver'};
1242 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1243 my $res = $known_server_db->select_dbentry($sql);
1244 if (keys(%$res) > 0) {
1245 my $regserver_key = $res->{1}->{'hostkey'};
1246 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1247 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1248 if ($source eq "GOSA") {
1249 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1250 }
1251 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1252 }
1253 $done = 1;
1254 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1255 } else {
1256 $not_found_in_foreign_clients_db = 1;
1257 }
1258 }
1260 # target is a server address -> forward to server
1261 if (not $done) {
1262 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1263 $res = $known_server_db->select_dbentry($sql);
1264 if (keys(%$res) > 0) {
1265 my $hostkey = $res->{1}->{'hostkey'};
1267 if ($source eq "GOSA") {
1268 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1269 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1271 }
1273 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1274 $done = 1;
1275 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1276 } else {
1277 $not_found_in_known_server_db = 1;
1278 }
1279 }
1282 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1283 if ( $not_found_in_foreign_clients_db
1284 && $not_found_in_known_server_db
1285 && $not_found_in_known_clients_db) {
1286 &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);
1287 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1288 $module = "GosaPackages";
1289 }
1290 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1291 primkey=>[],
1292 headertag=>$header,
1293 targettag=>$target,
1294 xmlmessage=>&encode_base64($msg),
1295 timestamp=>&get_time,
1296 module=>$module,
1297 sessionid=>$session_id,
1298 } );
1299 $done = 1;
1300 }
1303 if (not $done) {
1304 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1305 if ($source eq "GOSA") {
1306 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1307 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1309 my $session_reference = $kernel->ID_id_to_session($session_id);
1310 if( defined $session_reference ) {
1311 $heap = $session_reference->get_heap();
1312 }
1313 if(exists $heap->{'client'}) {
1314 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1315 $heap->{'client'}->put($error_msg);
1316 }
1317 }
1318 }
1320 }
1322 return;
1323 }
1326 sub next_task {
1327 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1328 my $running_task = POE::Wheel::Run->new(
1329 Program => sub { process_task($session, $heap, $task) },
1330 StdioFilter => POE::Filter::Reference->new(),
1331 StdoutEvent => "task_result",
1332 StderrEvent => "task_debug",
1333 CloseEvent => "task_done",
1334 );
1335 $heap->{task}->{ $running_task->ID } = $running_task;
1336 }
1338 sub handle_task_result {
1339 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1340 my $client_answer = $result->{'answer'};
1341 if( $client_answer =~ s/session_id=(\d+)$// ) {
1342 my $session_id = $1;
1343 if( defined $session_id ) {
1344 my $session_reference = $kernel->ID_id_to_session($session_id);
1345 if( defined $session_reference ) {
1346 $heap = $session_reference->get_heap();
1347 }
1348 }
1350 if(exists $heap->{'client'}) {
1351 $heap->{'client'}->put($client_answer);
1352 }
1353 }
1354 $kernel->sig(CHLD => "child_reap");
1355 }
1357 sub handle_task_debug {
1358 my $result = $_[ARG0];
1359 print STDERR "$result\n";
1360 }
1362 sub handle_task_done {
1363 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1364 delete $heap->{task}->{$task_id};
1365 }
1367 sub process_task {
1368 no strict "refs";
1369 #CHECK: Not @_[...]?
1370 my ($session, $heap, $task) = @_;
1371 my $error = 0;
1372 my $answer_l;
1373 my ($answer_header, @answer_target_l, $answer_source);
1374 my $client_answer = "";
1376 # prepare all variables needed to process message
1377 #my $msg = $task->{'xmlmessage'};
1378 my $msg = &decode_base64($task->{'xmlmessage'});
1379 my $incoming_id = $task->{'id'};
1380 my $module = $task->{'module'};
1381 my $header = $task->{'headertag'};
1382 my $session_id = $task->{'sessionid'};
1383 my $msg_hash;
1384 eval {
1385 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1386 };
1387 daemon_log("ERROR: XML failure '$@'") if ($@);
1388 my $source = @{$msg_hash->{'source'}}[0];
1390 # set timestamp of incoming client uptodate, so client will not
1391 # be deleted from known_clients because of expiration
1392 my $cur_time = &get_time();
1393 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1394 my $res = $known_clients_db->exec_statement($sql);
1396 ######################
1397 # process incoming msg
1398 if( $error == 0) {
1399 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1400 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1401 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1403 if ( 0 < @{$answer_l} ) {
1404 my $answer_str = join("\n", @{$answer_l});
1405 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1406 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1407 }
1408 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1409 } else {
1410 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1411 }
1413 }
1414 if( !$answer_l ) { $error++ };
1416 ########
1417 # answer
1418 if( $error == 0 ) {
1420 foreach my $answer ( @{$answer_l} ) {
1421 # check outgoing msg to xml validity
1422 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1423 if( not defined $answer_hash ) { next; }
1425 $answer_header = @{$answer_hash->{'header'}}[0];
1426 @answer_target_l = @{$answer_hash->{'target'}};
1427 $answer_source = @{$answer_hash->{'source'}}[0];
1429 # deliver msg to all targets
1430 foreach my $answer_target ( @answer_target_l ) {
1432 # targets of msg are all gosa-si-clients in known_clients_db
1433 if( $answer_target eq "*" ) {
1434 # answer is for all clients
1435 my $sql_statement= "SELECT * FROM known_clients";
1436 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1437 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1438 my $host_name = $hit->{hostname};
1439 my $host_key = $hit->{hostkey};
1440 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1441 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1442 }
1443 }
1445 # targets of msg are all gosa-si-server in known_server_db
1446 elsif( $answer_target eq "KNOWN_SERVER" ) {
1447 # answer is for all server in known_server
1448 my $sql_statement= "SELECT * FROM $known_server_tn";
1449 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1450 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1451 my $host_name = $hit->{hostname};
1452 my $host_key = $hit->{hostkey};
1453 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1454 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1455 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1456 }
1457 }
1459 # target of msg is GOsa
1460 elsif( $answer_target eq "GOSA" ) {
1461 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1462 my $add_on = "";
1463 if( defined $session_id ) {
1464 $add_on = ".session_id=$session_id";
1465 }
1466 # answer is for GOSA and has to returned to connected client
1467 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1468 $client_answer = $gosa_answer.$add_on;
1469 }
1471 # target of msg is job queue at this host
1472 elsif( $answer_target eq "JOBDB") {
1473 $answer =~ /<header>(\S+)<\/header>/;
1474 my $header;
1475 if( defined $1 ) { $header = $1; }
1476 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1477 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1478 }
1480 # Target of msg is a mac address
1481 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 ) {
1482 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1484 # Looking for macaddress in known_clients
1485 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1486 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1487 my $found_ip_flag = 0;
1488 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1489 my $host_name = $hit->{hostname};
1490 my $host_key = $hit->{hostkey};
1491 $answer =~ s/$answer_target/$host_name/g;
1492 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1493 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1494 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1495 $found_ip_flag++ ;
1496 }
1498 # Looking for macaddress in foreign_clients
1499 if ($found_ip_flag == 0) {
1500 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1501 my $res = $foreign_clients_db->select_dbentry($sql);
1502 while( my ($hit_num, $hit) = each %{ $res } ) {
1503 my $host_name = $hit->{hostname};
1504 my $reg_server = $hit->{regserver};
1505 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1507 # Fetch key for reg_server
1508 my $reg_server_key;
1509 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1510 my $res = $known_server_db->select_dbentry($sql);
1511 if (exists $res->{1}) {
1512 $reg_server_key = $res->{1}->{'hostkey'};
1513 } else {
1514 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1515 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1516 $reg_server_key = undef;
1517 }
1519 # Send answer to server where client is registered
1520 if (defined $reg_server_key) {
1521 $answer =~ s/$answer_target/$host_name/g;
1522 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1523 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1524 $found_ip_flag++ ;
1525 }
1526 }
1527 }
1529 # No mac to ip matching found
1530 if( $found_ip_flag == 0) {
1531 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1532 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1533 }
1535 # Answer is for one specific host
1536 } else {
1537 # get encrypt_key
1538 my $encrypt_key = &get_encrypt_key($answer_target);
1539 if( not defined $encrypt_key ) {
1540 # unknown target
1541 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1542 next;
1543 }
1544 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1545 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1546 }
1547 }
1548 }
1549 }
1551 my $filter = POE::Filter::Reference->new();
1552 my %result = (
1553 status => "seems ok to me",
1554 answer => $client_answer,
1555 );
1557 my $output = $filter->put( [ \%result ] );
1558 print @$output;
1561 }
1563 sub session_start {
1564 my ($kernel) = $_[KERNEL];
1565 $kernel->alias_set('gosa-si_server_session');
1566 $global_kernel = $kernel;
1567 $kernel->yield('register_at_foreign_servers');
1568 $kernel->yield('create_fai_server_db', $fai_server_tn );
1569 $kernel->yield('create_fai_release_db', $fai_release_tn );
1570 $kernel->yield('watch_for_next_tasks');
1571 $kernel->sig(USR2 => "recreate_packages_db");
1572 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1573 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1574 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1575 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1576 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1577 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1578 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1580 # Start opsi check
1581 if ($opsi_enabled eq "true") {
1582 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1583 }
1584 }
1586 sub session_stop {
1587 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1588 $kernel->alias_remove($heap->{alias});
1589 $kernel->alarm_remove_all();
1590 $kernel->post($heap->{child_session}, '_stop');
1591 }
1593 sub watch_for_done_jobs {
1594 #CHECK: $heap for what?
1595 my ($kernel,$heap) = @_[KERNEL, HEAP];
1597 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1598 my $res = $job_db->select_dbentry( $sql_statement );
1600 while( my ($id, $hit) = each %{$res} ) {
1601 my $jobdb_id = $hit->{id};
1602 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1603 my $res = $job_db->del_dbentry($sql_statement);
1604 }
1606 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1607 }
1610 sub watch_for_opsi_jobs {
1611 my ($kernel) = $_[KERNEL];
1613 # 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
1614 # opsi install job is to parse the xml message. There is still the correct header.
1615 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1616 my $res = $job_db->select_dbentry( $sql_statement );
1618 # Ask OPSI for an update of the running jobs
1619 while (my ($id, $hit) = each %$res ) {
1620 # Determine current parameters of the job
1621 my $hostId = $hit->{'plainname'};
1622 my $macaddress = $hit->{'macaddress'};
1623 my $progress = $hit->{'progress'};
1625 my $result= {};
1627 # For hosts, only return the products that are or get installed
1628 my $callobj;
1629 $callobj = {
1630 method => 'getProductStates_hash',
1631 params => [ $hostId ],
1632 id => 1,
1633 };
1635 my $hres = $opsi_client->call($opsi_url, $callobj);
1636 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1637 if (not &check_opsi_res($hres)) {
1638 my $htmp= $hres->result->{$hostId};
1640 # Check state != not_installed or action == setup -> load and add
1641 my $products= 0;
1642 my $installed= 0;
1643 my $installing = 0;
1644 my $error= 0;
1645 my @installed_list;
1646 my @error_list;
1647 my $act_status = "none";
1648 foreach my $product (@{$htmp}){
1650 if ($product->{'installationStatus'} ne "not_installed" or
1651 $product->{'actionRequest'} eq "setup"){
1653 # Increase number of products for this host
1654 $products++;
1656 if ($product->{'installationStatus'} eq "failed"){
1657 $result->{$product->{'productId'}}= "error";
1658 unshift(@error_list, $product->{'productId'});
1659 $error++;
1660 }
1661 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1662 $result->{$product->{'productId'}}= "installed";
1663 unshift(@installed_list, $product->{'productId'});
1664 $installed++;
1665 }
1666 if ($product->{'installationStatus'} eq "installing"){
1667 $result->{$product->{'productId'}}= "installing";
1668 $installing++;
1669 $act_status = "installing - ".$product->{'productId'};
1670 }
1671 }
1672 }
1674 # Estimate "rough" progress, avoid division by zero
1675 if ($products == 0) {
1676 $result->{'progress'}= 0;
1677 } else {
1678 $result->{'progress'}= int($installed * 100 / $products);
1679 }
1681 # Set updates in job queue
1682 if ((not $error) && (not $installing) && ($installed)) {
1683 $act_status = "installed - ".join(", ", @installed_list);
1684 }
1685 if ($error) {
1686 $act_status = "error - ".join(", ", @error_list);
1687 }
1688 if ($progress ne $result->{'progress'} ) {
1689 # Updating progress and result
1690 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1691 my $update_res = $job_db->update_dbentry($update_statement);
1692 }
1693 if ($progress eq 100) {
1694 # Updateing status
1695 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1696 if ($error) {
1697 $done_statement .= "status='error'";
1698 } else {
1699 $done_statement .= "status='done'";
1700 }
1701 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1702 my $done_res = $job_db->update_dbentry($done_statement);
1703 }
1706 }
1707 }
1709 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1710 }
1713 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1714 sub watch_for_modified_jobs {
1715 my ($kernel,$heap) = @_[KERNEL, HEAP];
1717 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1718 my $res = $job_db->select_dbentry( $sql_statement );
1720 # if db contains no jobs which should be update, do nothing
1721 if (keys %$res != 0) {
1723 if ($job_synchronization eq "true") {
1724 # make out of the db result a gosa-si message
1725 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1727 # update all other SI-server
1728 &inform_all_other_si_server($update_msg);
1729 }
1731 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1732 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1733 $res = $job_db->update_dbentry($sql_statement);
1734 }
1736 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1737 }
1740 sub watch_for_new_jobs {
1741 if($watch_for_new_jobs_in_progress == 0) {
1742 $watch_for_new_jobs_in_progress = 1;
1743 my ($kernel,$heap) = @_[KERNEL, HEAP];
1745 # check gosa job quaeue for jobs with executable timestamp
1746 my $timestamp = &get_time();
1747 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1748 my $res = $job_db->exec_statement( $sql_statement );
1750 # Merge all new jobs that would do the same actions
1751 my @drops;
1752 my $hits;
1753 foreach my $hit (reverse @{$res} ) {
1754 my $macaddress= lc @{$hit}[8];
1755 my $headertag= @{$hit}[5];
1756 if(
1757 defined($hits->{$macaddress}) &&
1758 defined($hits->{$macaddress}->{$headertag}) &&
1759 defined($hits->{$macaddress}->{$headertag}[0])
1760 ) {
1761 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1762 }
1763 $hits->{$macaddress}->{$headertag}= $hit;
1764 }
1766 # Delete new jobs with a matching job in state 'processing'
1767 foreach my $macaddress (keys %{$hits}) {
1768 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1769 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1770 if(defined($jobdb_id)) {
1771 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1772 my $res = $job_db->exec_statement( $sql_statement );
1773 foreach my $hit (@{$res}) {
1774 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1775 }
1776 } else {
1777 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1778 }
1779 }
1780 }
1782 # Commit deletion
1783 $job_db->exec_statementlist(\@drops);
1785 # Look for new jobs that could be executed
1786 foreach my $macaddress (keys %{$hits}) {
1788 # Look if there is an executing job
1789 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1790 my $res = $job_db->exec_statement( $sql_statement );
1792 # Skip new jobs for host if there is a processing job
1793 if(defined($res) and defined @{$res}[0]) {
1794 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1795 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1796 if(@{$row}[5] eq 'trigger_action_reinstall') {
1797 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1798 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1799 if(defined($res_2) and defined @{$res_2}[0]) {
1800 # Set status from goto-activation to 'waiting' and update timestamp
1801 $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'");
1802 }
1803 }
1804 next;
1805 }
1807 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1808 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1809 if(defined($jobdb_id)) {
1810 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1812 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1813 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1814 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1816 # expect macaddress is unique!!!!!!
1817 my $target = $res_hash->{1}->{hostname};
1819 # change header
1820 $job_msg =~ s/<header>job_/<header>gosa_/;
1822 # add sqlite_id
1823 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1825 $job_msg =~ /<header>(\S+)<\/header>/;
1826 my $header = $1 ;
1827 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1829 # update status in job queue to ...
1830 # ... 'processing', for jobs: 'reinstall', 'update'
1831 if (($header =~ /gosa_trigger_action_reinstall/)
1832 || ($header =~ /gosa_trigger_activate_new/)
1833 || ($header =~ /gosa_trigger_action_update/)) {
1834 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1835 my $dbres = $job_db->update_dbentry($sql_statement);
1836 }
1838 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1839 else {
1840 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1841 my $dbres = $job_db->update_dbentry($sql_statement);
1842 }
1845 # We don't want parallel processing
1846 last;
1847 }
1848 }
1849 }
1851 $watch_for_new_jobs_in_progress = 0;
1852 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1853 }
1854 }
1857 sub watch_for_new_messages {
1858 my ($kernel,$heap) = @_[KERNEL, HEAP];
1859 my @coll_user_msg; # collection list of outgoing messages
1861 # check messaging_db for new incoming messages with executable timestamp
1862 my $timestamp = &get_time();
1863 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1864 my $res = $messaging_db->exec_statement( $sql_statement );
1865 foreach my $hit (@{$res}) {
1867 # create outgoing messages
1868 my $message_to = @{$hit}[3];
1869 # translate message_to to plain login name
1870 my @message_to_l = split(/,/, $message_to);
1871 my %receiver_h;
1872 foreach my $receiver (@message_to_l) {
1873 if ($receiver =~ /^u_([\s\S]*)$/) {
1874 $receiver_h{$1} = 0;
1875 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1876 my $group_name = $1;
1877 # fetch all group members from ldap and add them to receiver hash
1878 my $ldap_handle = &get_ldap_handle();
1879 if (defined $ldap_handle) {
1880 my $mesg = $ldap_handle->search(
1881 base => $ldap_base,
1882 scope => 'sub',
1883 attrs => ['memberUid'],
1884 filter => "cn=$group_name",
1885 );
1886 if ($mesg->count) {
1887 my @entries = $mesg->entries;
1888 foreach my $entry (@entries) {
1889 my @receivers= $entry->get_value("memberUid");
1890 foreach my $receiver (@receivers) {
1891 $receiver_h{$receiver} = 0;
1892 }
1893 }
1894 }
1895 # translating errors ?
1896 if ($mesg->code) {
1897 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1898 }
1899 # ldap handle error ?
1900 } else {
1901 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1902 }
1903 } else {
1904 my $sbjct = &encode_base64(@{$hit}[1]);
1905 my $msg = &encode_base64(@{$hit}[7]);
1906 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1907 }
1908 }
1909 my @receiver_l = keys(%receiver_h);
1911 my $message_id = @{$hit}[0];
1913 #add each outgoing msg to messaging_db
1914 my $receiver;
1915 foreach $receiver (@receiver_l) {
1916 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1917 "VALUES ('".
1918 $message_id."', '". # id
1919 @{$hit}[1]."', '". # subject
1920 @{$hit}[2]."', '". # message_from
1921 $receiver."', '". # message_to
1922 "none"."', '". # flag
1923 "out"."', '". # direction
1924 @{$hit}[6]."', '". # delivery_time
1925 @{$hit}[7]."', '". # message
1926 $timestamp."'". # timestamp
1927 ")";
1928 &daemon_log("M DEBUG: $sql_statement", 1);
1929 my $res = $messaging_db->exec_statement($sql_statement);
1930 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1931 }
1933 # set incoming message to flag d=deliverd
1934 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1935 &daemon_log("M DEBUG: $sql_statement", 7);
1936 $res = $messaging_db->update_dbentry($sql_statement);
1937 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1938 }
1940 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1941 return;
1942 }
1944 sub watch_for_delivery_messages {
1945 my ($kernel, $heap) = @_[KERNEL, HEAP];
1947 # select outgoing messages
1948 my $timestamp= &get_time();
1949 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' AND delivery_time<$timestamp)";
1950 #&daemon_log("0 DEBUG: $sql", 7);
1951 my $res = $messaging_db->exec_statement( $sql_statement );
1953 # build out msg for each usr
1954 foreach my $hit (@{$res}) {
1955 my $receiver = @{$hit}[3];
1956 my $msg_id = @{$hit}[0];
1957 my $subject = @{$hit}[1];
1958 my $message = @{$hit}[7];
1960 # resolve usr -> host where usr is logged in
1961 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1962 #&daemon_log("0 DEBUG: $sql", 7);
1963 my $res = $login_users_db->exec_statement($sql);
1965 # receiver is logged in nowhere
1966 if (not ref(@$res[0]) eq "ARRAY") { next; }
1968 # receiver ist logged in at a client registered at local server
1969 my $send_succeed = 0;
1970 foreach my $hit (@$res) {
1971 my $receiver_host = @$hit[0];
1972 my $delivered2host = 0;
1973 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1975 # Looking for host in know_clients_db
1976 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1977 my $res = $known_clients_db->exec_statement($sql);
1979 # Host is known in known_clients_db
1980 if (ref(@$res[0]) eq "ARRAY") {
1981 my $receiver_key = @{@{$res}[0]}[2];
1982 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1983 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1984 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1985 if ($error == 0 ) {
1986 $send_succeed++ ;
1987 $delivered2host++ ;
1988 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1989 } else {
1990 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1991 }
1992 }
1994 # Message already send, do not need to do anything more, otherwise ...
1995 if ($delivered2host) { next;}
1997 # ...looking for host in foreign_clients_db
1998 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1999 $res = $foreign_clients_db->exec_statement($sql);
2001 # Host is known in foreign_clients_db
2002 if (ref(@$res[0]) eq "ARRAY") {
2003 my $registration_server = @{@{$res}[0]}[2];
2005 # Fetch encryption key for registration server
2006 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2007 my $res = $known_server_db->exec_statement($sql);
2008 if (ref(@$res[0]) eq "ARRAY") {
2009 my $registration_server_key = @{@{$res}[0]}[3];
2010 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2011 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2012 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2013 if ($error == 0 ) {
2014 $send_succeed++ ;
2015 $delivered2host++ ;
2016 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2017 } else {
2018 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2019 }
2021 } else {
2022 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2023 "registrated at server '$registration_server', ".
2024 "but no data available in known_server_db ", 1);
2025 }
2026 }
2028 if (not $delivered2host) {
2029 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2030 }
2031 }
2033 if ($send_succeed) {
2034 # set outgoing msg at db to deliverd
2035 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2036 my $res = $messaging_db->exec_statement($sql);
2037 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2038 } else {
2039 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2040 }
2041 }
2043 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2044 return;
2045 }
2048 sub watch_for_done_messages {
2049 my ($kernel,$heap) = @_[KERNEL, HEAP];
2051 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2052 #&daemon_log("0 DEBUG: $sql", 7);
2053 my $res = $messaging_db->exec_statement($sql);
2055 foreach my $hit (@{$res}) {
2056 my $msg_id = @{$hit}[0];
2058 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2059 #&daemon_log("0 DEBUG: $sql", 7);
2060 my $res = $messaging_db->exec_statement($sql);
2062 # not all usr msgs have been seen till now
2063 if ( ref(@$res[0]) eq "ARRAY") { next; }
2065 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2066 #&daemon_log("0 DEBUG: $sql", 7);
2067 $res = $messaging_db->exec_statement($sql);
2069 }
2071 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2072 return;
2073 }
2076 sub watch_for_old_known_clients {
2077 my ($kernel,$heap) = @_[KERNEL, HEAP];
2079 my $sql_statement = "SELECT * FROM $known_clients_tn";
2080 my $res = $known_clients_db->select_dbentry( $sql_statement );
2082 my $cur_time = int(&get_time());
2084 while ( my ($hit_num, $hit) = each %$res) {
2085 my $expired_timestamp = int($hit->{'timestamp'});
2086 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2087 my $dt = DateTime->new( year => $1,
2088 month => $2,
2089 day => $3,
2090 hour => $4,
2091 minute => $5,
2092 second => $6,
2093 );
2095 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2096 $expired_timestamp = $dt->ymd('').$dt->hms('');
2097 if ($cur_time > $expired_timestamp) {
2098 my $hostname = $hit->{'hostname'};
2099 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2100 my $del_res = $known_clients_db->exec_statement($del_sql);
2102 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2103 }
2105 }
2107 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2108 }
2111 sub watch_for_next_tasks {
2112 my ($kernel,$heap) = @_[KERNEL, HEAP];
2114 my $sql = "SELECT * FROM $incoming_tn";
2115 my $res = $incoming_db->select_dbentry($sql);
2117 while ( my ($hit_num, $hit) = each %$res) {
2118 my $headertag = $hit->{'headertag'};
2119 if ($headertag =~ /^answer_(\d+)/) {
2120 # do not start processing, this message is for a still running POE::Wheel
2121 next;
2122 }
2123 my $message_id = $hit->{'id'};
2124 my $session_id = $hit->{'sessionid'};
2125 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2126 $kernel->yield('next_task', $hit);
2128 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2129 my $res = $incoming_db->exec_statement($sql);
2130 }
2132 $kernel->delay_set('watch_for_next_tasks', 1);
2133 }
2136 sub get_ldap_handle {
2137 my ($session_id) = @_;
2138 my $heap;
2139 my $ldap_handle;
2141 if (not defined $session_id ) { $session_id = 0 };
2142 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2144 if ($session_id == 0) {
2145 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2146 $ldap_handle = Net::LDAP->new( $ldap_uri );
2147 if (defined $ldap_handle) {
2148 $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!");
2149 } else {
2150 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2151 }
2153 } else {
2154 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2155 if( defined $session_reference ) {
2156 $heap = $session_reference->get_heap();
2157 }
2159 if (not defined $heap) {
2160 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2161 return;
2162 }
2164 # TODO: This "if" is nonsense, because it doesn't prove that the
2165 # used handle is still valid - or if we've to reconnect...
2166 #if (not exists $heap->{ldap_handle}) {
2167 $ldap_handle = Net::LDAP->new( $ldap_uri );
2168 $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!");
2169 $heap->{ldap_handle} = $ldap_handle;
2170 #}
2171 }
2172 return $ldap_handle;
2173 }
2176 sub change_fai_state {
2177 my ($st, $targets, $session_id) = @_;
2178 $session_id = 0 if not defined $session_id;
2179 # Set FAI state to localboot
2180 my %mapActions= (
2181 reboot => '',
2182 update => 'softupdate',
2183 localboot => 'localboot',
2184 reinstall => 'install',
2185 rescan => '',
2186 wake => '',
2187 memcheck => 'memcheck',
2188 sysinfo => 'sysinfo',
2189 install => 'install',
2190 );
2192 # Return if this is unknown
2193 if (!exists $mapActions{ $st }){
2194 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2195 return;
2196 }
2198 my $state= $mapActions{ $st };
2200 my $ldap_handle = &get_ldap_handle($session_id);
2201 if( defined($ldap_handle) ) {
2203 # Build search filter for hosts
2204 my $search= "(&(objectClass=GOhard)";
2205 foreach (@{$targets}){
2206 $search.= "(macAddress=$_)";
2207 }
2208 $search.= ")";
2210 # If there's any host inside of the search string, procress them
2211 if (!($search =~ /macAddress/)){
2212 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2213 return;
2214 }
2216 # Perform search for Unit Tag
2217 my $mesg = $ldap_handle->search(
2218 base => $ldap_base,
2219 scope => 'sub',
2220 attrs => ['dn', 'FAIstate', 'objectClass'],
2221 filter => "$search"
2222 );
2224 if ($mesg->count) {
2225 my @entries = $mesg->entries;
2226 if (0 == @entries) {
2227 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2228 }
2230 foreach my $entry (@entries) {
2231 # Only modify entry if it is not set to '$state'
2232 if ($entry->get_value("FAIstate") ne "$state"){
2233 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2234 my $result;
2235 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2236 if (exists $tmp{'FAIobject'}){
2237 if ($state eq ''){
2238 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2239 } else {
2240 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2241 }
2242 } elsif ($state ne ''){
2243 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2244 }
2246 # Errors?
2247 if ($result->code){
2248 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2249 }
2250 } else {
2251 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2252 }
2253 }
2254 } else {
2255 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2256 }
2258 # if no ldap handle defined
2259 } else {
2260 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2261 }
2263 return;
2264 }
2267 sub change_goto_state {
2268 my ($st, $targets, $session_id) = @_;
2269 $session_id = 0 if not defined $session_id;
2271 # Switch on or off?
2272 my $state= $st eq 'active' ? 'active': 'locked';
2274 my $ldap_handle = &get_ldap_handle($session_id);
2275 if( defined($ldap_handle) ) {
2277 # Build search filter for hosts
2278 my $search= "(&(objectClass=GOhard)";
2279 foreach (@{$targets}){
2280 $search.= "(macAddress=$_)";
2281 }
2282 $search.= ")";
2284 # If there's any host inside of the search string, procress them
2285 if (!($search =~ /macAddress/)){
2286 return;
2287 }
2289 # Perform search for Unit Tag
2290 my $mesg = $ldap_handle->search(
2291 base => $ldap_base,
2292 scope => 'sub',
2293 attrs => ['dn', 'gotoMode'],
2294 filter => "$search"
2295 );
2297 if ($mesg->count) {
2298 my @entries = $mesg->entries;
2299 foreach my $entry (@entries) {
2301 # Only modify entry if it is not set to '$state'
2302 if ($entry->get_value("gotoMode") ne $state){
2304 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2305 my $result;
2306 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2308 # Errors?
2309 if ($result->code){
2310 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2311 }
2313 }
2314 }
2315 } else {
2316 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2317 }
2319 }
2320 }
2323 sub run_recreate_packages_db {
2324 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2325 my $session_id = $session->ID;
2326 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2327 $kernel->yield('create_fai_release_db', $fai_release_tn);
2328 $kernel->yield('create_fai_server_db', $fai_server_tn);
2329 return;
2330 }
2333 sub run_create_fai_server_db {
2334 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2335 my $session_id = $session->ID;
2336 my $task = POE::Wheel::Run->new(
2337 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2338 StdoutEvent => "session_run_result",
2339 StderrEvent => "session_run_debug",
2340 CloseEvent => "session_run_done",
2341 );
2343 $heap->{task}->{ $task->ID } = $task;
2344 return;
2345 }
2348 sub create_fai_server_db {
2349 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2350 my $result;
2352 if (not defined $session_id) { $session_id = 0; }
2353 my $ldap_handle = &get_ldap_handle();
2354 if(defined($ldap_handle)) {
2355 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2356 my $mesg= $ldap_handle->search(
2357 base => $ldap_base,
2358 scope => 'sub',
2359 attrs => ['FAIrepository', 'gosaUnitTag'],
2360 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2361 );
2362 if($mesg->{'resultCode'} == 0 &&
2363 $mesg->count != 0) {
2364 foreach my $entry (@{$mesg->{entries}}) {
2365 if($entry->exists('FAIrepository')) {
2366 # Add an entry for each Repository configured for server
2367 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2368 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2369 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2370 $result= $fai_server_db->add_dbentry( {
2371 table => $table_name,
2372 primkey => ['server', 'fai_release', 'tag'],
2373 server => $tmp_url,
2374 fai_release => $tmp_release,
2375 sections => $tmp_sections,
2376 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2377 } );
2378 }
2379 }
2380 }
2381 }
2382 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2384 # TODO: Find a way to post the 'create_packages_list_db' event
2385 if(not defined($dont_create_packages_list)) {
2386 &create_packages_list_db(undef, undef, $session_id);
2387 }
2388 }
2390 $ldap_handle->disconnect;
2391 return $result;
2392 }
2395 sub run_create_fai_release_db {
2396 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2397 my $session_id = $session->ID;
2398 my $task = POE::Wheel::Run->new(
2399 Program => sub { &create_fai_release_db($table_name, $session_id) },
2400 StdoutEvent => "session_run_result",
2401 StderrEvent => "session_run_debug",
2402 CloseEvent => "session_run_done",
2403 );
2405 $heap->{task}->{ $task->ID } = $task;
2406 return;
2407 }
2410 sub create_fai_release_db {
2411 my ($table_name, $session_id) = @_;
2412 my $result;
2414 # used for logging
2415 if (not defined $session_id) { $session_id = 0; }
2417 my $ldap_handle = &get_ldap_handle();
2418 if(defined($ldap_handle)) {
2419 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2420 my $mesg= $ldap_handle->search(
2421 base => $ldap_base,
2422 scope => 'sub',
2423 attrs => [],
2424 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2425 );
2426 if(($mesg->code == 0) && ($mesg->count != 0))
2427 {
2428 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,8);
2430 # Walk through all possible FAI container ou's
2431 my @sql_list;
2432 my $timestamp= &get_time();
2433 foreach my $ou (@{$mesg->{entries}}) {
2434 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2435 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2436 my @tmp_array=get_fai_release_entries($tmp_classes);
2437 if(@tmp_array) {
2438 foreach my $entry (@tmp_array) {
2439 if(defined($entry) && ref($entry) eq 'HASH') {
2440 my $sql=
2441 "INSERT INTO $table_name "
2442 ."(timestamp, fai_release, class, type, state) VALUES ("
2443 .$timestamp.","
2444 ."'".$entry->{'release'}."',"
2445 ."'".$entry->{'class'}."',"
2446 ."'".$entry->{'type'}."',"
2447 ."'".$entry->{'state'}."')";
2448 push @sql_list, $sql;
2449 }
2450 }
2451 }
2452 }
2453 }
2455 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",8);
2456 if(@sql_list) {
2457 unshift @sql_list, "VACUUM";
2458 unshift @sql_list, "DELETE FROM $table_name";
2459 $fai_release_db->exec_statementlist(\@sql_list);
2460 }
2461 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",7);
2462 } else {
2463 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2464 }
2465 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2466 }
2467 $ldap_handle->disconnect;
2468 return $result;
2469 }
2471 sub get_fai_types {
2472 my $tmp_classes = shift || return undef;
2473 my @result;
2475 foreach my $type(keys %{$tmp_classes}) {
2476 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2477 my $entry = {
2478 type => $type,
2479 state => $tmp_classes->{$type}[0],
2480 };
2481 push @result, $entry;
2482 }
2483 }
2485 return @result;
2486 }
2488 sub get_fai_state {
2489 my $result = "";
2490 my $tmp_classes = shift || return $result;
2492 foreach my $type(keys %{$tmp_classes}) {
2493 if(defined($tmp_classes->{$type}[0])) {
2494 $result = $tmp_classes->{$type}[0];
2496 # State is equal for all types in class
2497 last;
2498 }
2499 }
2501 return $result;
2502 }
2504 sub resolve_fai_classes {
2505 my ($fai_base, $ldap_handle, $session_id) = @_;
2506 if (not defined $session_id) { $session_id = 0; }
2507 my $result;
2508 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2509 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2510 my $fai_classes;
2512 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2513 my $mesg= $ldap_handle->search(
2514 base => $fai_base,
2515 scope => 'sub',
2516 attrs => ['cn','objectClass','FAIstate'],
2517 filter => $fai_filter,
2518 );
2519 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2521 if($mesg->{'resultCode'} == 0 &&
2522 $mesg->count != 0) {
2523 foreach my $entry (@{$mesg->{entries}}) {
2524 if($entry->exists('cn')) {
2525 my $tmp_dn= $entry->dn();
2526 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2527 - length($fai_base) - 1 );
2529 # Skip classname and ou dn parts for class
2530 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2532 # Skip classes without releases
2533 if((!defined($tmp_release)) || length($tmp_release)==0) {
2534 next;
2535 }
2537 my $tmp_cn= $entry->get_value('cn');
2538 my $tmp_state= $entry->get_value('FAIstate');
2540 my $tmp_type;
2541 # Get FAI type
2542 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2543 if(grep $_ eq $oclass, @possible_fai_classes) {
2544 $tmp_type= $oclass;
2545 last;
2546 }
2547 }
2549 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2550 # A Subrelease
2551 my @sub_releases = split(/,/, $tmp_release);
2553 # Walk through subreleases and build hash tree
2554 my $hash;
2555 while(my $tmp_sub_release = pop @sub_releases) {
2556 $hash .= "\{'$tmp_sub_release'\}->";
2557 }
2558 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2559 } else {
2560 # A branch, no subrelease
2561 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2562 }
2563 } elsif (!$entry->exists('cn')) {
2564 my $tmp_dn= $entry->dn();
2565 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2566 - length($fai_base) - 1 );
2567 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2569 # Skip classes without releases
2570 if((!defined($tmp_release)) || length($tmp_release)==0) {
2571 next;
2572 }
2574 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2575 # A Subrelease
2576 my @sub_releases= split(/,/, $tmp_release);
2578 # Walk through subreleases and build hash tree
2579 my $hash;
2580 while(my $tmp_sub_release = pop @sub_releases) {
2581 $hash .= "\{'$tmp_sub_release'\}->";
2582 }
2583 # Remove the last two characters
2584 chop($hash);
2585 chop($hash);
2587 eval('$fai_classes->'.$hash.'= {}');
2588 } else {
2589 # A branch, no subrelease
2590 if(!exists($fai_classes->{$tmp_release})) {
2591 $fai_classes->{$tmp_release} = {};
2592 }
2593 }
2594 }
2595 }
2597 # The hash is complete, now we can honor the copy-on-write based missing entries
2598 foreach my $release (keys %$fai_classes) {
2599 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2600 }
2601 }
2602 return $result;
2603 }
2605 sub apply_fai_inheritance {
2606 my $fai_classes = shift || return {};
2607 my $tmp_classes;
2609 # Get the classes from the branch
2610 foreach my $class (keys %{$fai_classes}) {
2611 # Skip subreleases
2612 if($class =~ /^ou=.*$/) {
2613 next;
2614 } else {
2615 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2616 }
2617 }
2619 # Apply to each subrelease
2620 foreach my $subrelease (keys %{$fai_classes}) {
2621 if($subrelease =~ /ou=/) {
2622 foreach my $tmp_class (keys %{$tmp_classes}) {
2623 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2624 $fai_classes->{$subrelease}->{$tmp_class} =
2625 deep_copy($tmp_classes->{$tmp_class});
2626 } else {
2627 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2628 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2629 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2630 deep_copy($tmp_classes->{$tmp_class}->{$type});
2631 }
2632 }
2633 }
2634 }
2635 }
2636 }
2638 # Find subreleases in deeper levels
2639 foreach my $subrelease (keys %{$fai_classes}) {
2640 if($subrelease =~ /ou=/) {
2641 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2642 if($subsubrelease =~ /ou=/) {
2643 apply_fai_inheritance($fai_classes->{$subrelease});
2644 }
2645 }
2646 }
2647 }
2649 return $fai_classes;
2650 }
2652 sub get_fai_release_entries {
2653 my $tmp_classes = shift || return;
2654 my $parent = shift || "";
2655 my @result = shift || ();
2657 foreach my $entry (keys %{$tmp_classes}) {
2658 if(defined($entry)) {
2659 if($entry =~ /^ou=.*$/) {
2660 my $release_name = $entry;
2661 $release_name =~ s/ou=//g;
2662 if(length($parent)>0) {
2663 $release_name = $parent."/".$release_name;
2664 }
2665 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2666 foreach my $bufentry(@bufentries) {
2667 push @result, $bufentry;
2668 }
2669 } else {
2670 my @types = get_fai_types($tmp_classes->{$entry});
2671 foreach my $type (@types) {
2672 push @result,
2673 {
2674 'class' => $entry,
2675 'type' => $type->{'type'},
2676 'release' => $parent,
2677 'state' => $type->{'state'},
2678 };
2679 }
2680 }
2681 }
2682 }
2684 return @result;
2685 }
2687 sub deep_copy {
2688 my $this = shift;
2689 if (not ref $this) {
2690 $this;
2691 } elsif (ref $this eq "ARRAY") {
2692 [map deep_copy($_), @$this];
2693 } elsif (ref $this eq "HASH") {
2694 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2695 } else { die "what type is $_?" }
2696 }
2699 sub session_run_result {
2700 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2701 $kernel->sig(CHLD => "child_reap");
2702 }
2704 sub session_run_debug {
2705 my $result = $_[ARG0];
2706 print STDERR "$result\n";
2707 }
2709 sub session_run_done {
2710 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2711 delete $heap->{task}->{$task_id};
2712 }
2715 sub create_sources_list {
2716 my $session_id = shift;
2717 my $ldap_handle = &main::get_ldap_handle;
2718 my $result="/tmp/gosa_si_tmp_sources_list";
2720 # Remove old file
2721 if(stat($result)) {
2722 unlink($result);
2723 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2724 }
2726 my $fh;
2727 open($fh, ">$result");
2728 if (not defined $fh) {
2729 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2730 return undef;
2731 }
2732 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2733 my $mesg=$ldap_handle->search(
2734 base => $main::ldap_server_dn,
2735 scope => 'base',
2736 attrs => 'FAIrepository',
2737 filter => 'objectClass=FAIrepositoryServer'
2738 );
2739 if($mesg->count) {
2740 foreach my $entry(@{$mesg->{'entries'}}) {
2741 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2742 my ($server, $tag, $release, $sections)= split /\|/, $value;
2743 my $line = "deb $server $release";
2744 $sections =~ s/,/ /g;
2745 $line.= " $sections";
2746 print $fh $line."\n";
2747 }
2748 }
2749 }
2750 } else {
2751 if (defined $main::ldap_server_dn){
2752 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2753 } else {
2754 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2755 }
2756 }
2757 close($fh);
2759 return $result;
2760 }
2763 sub run_create_packages_list_db {
2764 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2765 my $session_id = $session->ID;
2767 my $task = POE::Wheel::Run->new(
2768 Priority => +20,
2769 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2770 StdoutEvent => "session_run_result",
2771 StderrEvent => "session_run_debug",
2772 CloseEvent => "session_run_done",
2773 );
2774 $heap->{task}->{ $task->ID } = $task;
2775 }
2778 sub create_packages_list_db {
2779 my ($ldap_handle, $sources_file, $session_id) = @_;
2781 # it should not be possible to trigger a recreation of packages_list_db
2782 # while packages_list_db is under construction, so set flag packages_list_under_construction
2783 # which is tested befor recreation can be started
2784 if (-r $packages_list_under_construction) {
2785 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2786 return;
2787 } else {
2788 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2789 # set packages_list_under_construction to true
2790 system("touch $packages_list_under_construction");
2791 @packages_list_statements=();
2792 }
2794 if (not defined $session_id) { $session_id = 0; }
2795 if (not defined $ldap_handle) {
2796 $ldap_handle= &get_ldap_handle();
2798 if (not defined $ldap_handle) {
2799 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2800 unlink($packages_list_under_construction);
2801 return;
2802 }
2803 }
2804 if (not defined $sources_file) {
2805 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2806 $sources_file = &create_sources_list($session_id);
2807 }
2809 if (not defined $sources_file) {
2810 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2811 unlink($packages_list_under_construction);
2812 return;
2813 }
2815 my $line;
2817 open(CONFIG, "<$sources_file") or do {
2818 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2819 unlink($packages_list_under_construction);
2820 return;
2821 };
2823 # Read lines
2824 while ($line = <CONFIG>){
2825 # Unify
2826 chop($line);
2827 $line =~ s/^\s+//;
2828 $line =~ s/^\s+/ /;
2830 # Strip comments
2831 $line =~ s/#.*$//g;
2833 # Skip empty lines
2834 if ($line =~ /^\s*$/){
2835 next;
2836 }
2838 # Interpret deb line
2839 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2840 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2841 my $section;
2842 foreach $section (split(' ', $sections)){
2843 &parse_package_info( $baseurl, $dist, $section, $session_id );
2844 }
2845 }
2846 }
2848 close (CONFIG);
2850 if(keys(%repo_dirs)) {
2851 find(\&cleanup_and_extract, keys( %repo_dirs ));
2852 &main::strip_packages_list_statements();
2853 $packages_list_db->exec_statementlist(\@packages_list_statements);
2854 }
2855 unlink($packages_list_under_construction);
2856 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2857 return;
2858 }
2860 # This function should do some intensive task to minimize the db-traffic
2861 sub strip_packages_list_statements {
2862 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2863 my @new_statement_list=();
2864 my $hash;
2865 my $insert_hash;
2866 my $update_hash;
2867 my $delete_hash;
2868 my $known_packages_hash;
2869 my $local_timestamp=get_time();
2871 foreach my $existing_entry (@existing_entries) {
2872 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2873 }
2875 foreach my $statement (@packages_list_statements) {
2876 if($statement =~ /^INSERT/i) {
2877 # Assign the values from the insert statement
2878 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2879 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2880 if(exists($hash->{$distribution}->{$package}->{$version})) {
2881 # If section or description has changed, update the DB
2882 if(
2883 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2884 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2885 ) {
2886 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2887 } else {
2888 # package is already present in database. cache this knowledge for later use
2889 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2890 }
2891 } else {
2892 # Insert a non-existing entry to db
2893 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2894 }
2895 } elsif ($statement =~ /^UPDATE/i) {
2896 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2897 /^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;
2898 foreach my $distribution (keys %{$hash}) {
2899 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2900 # update the insertion hash to execute only one query per package (insert instead insert+update)
2901 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2902 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2903 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2904 my $section;
2905 my $description;
2906 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2907 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2908 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2909 }
2910 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2911 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2912 }
2913 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2914 }
2915 }
2916 }
2917 }
2918 }
2920 # Check for orphaned entries
2921 foreach my $existing_entry (@existing_entries) {
2922 my $distribution= @{$existing_entry}[0];
2923 my $package= @{$existing_entry}[1];
2924 my $version= @{$existing_entry}[2];
2925 my $section= @{$existing_entry}[3];
2927 if(
2928 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2929 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2930 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2931 ) {
2932 next;
2933 } else {
2934 # Insert entry to delete hash
2935 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2936 }
2937 }
2939 # unroll the insert hash
2940 foreach my $distribution (keys %{$insert_hash}) {
2941 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2942 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2943 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2944 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2945 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2946 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2947 ."'$local_timestamp')";
2948 }
2949 }
2950 }
2952 # unroll the update hash
2953 foreach my $distribution (keys %{$update_hash}) {
2954 foreach my $package (keys %{$update_hash->{$distribution}}) {
2955 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2956 my $set = "";
2957 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2958 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2959 }
2960 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2961 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2962 }
2963 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2964 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2965 }
2966 if(defined($set) and length($set) > 0) {
2967 $set .= "timestamp = '$local_timestamp'";
2968 } else {
2969 next;
2970 }
2971 push @new_statement_list,
2972 "UPDATE $main::packages_list_tn SET $set WHERE"
2973 ." distribution = '$distribution'"
2974 ." AND package = '$package'"
2975 ." AND version = '$version'";
2976 }
2977 }
2978 }
2980 # unroll the delete hash
2981 foreach my $distribution (keys %{$delete_hash}) {
2982 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2983 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2984 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2985 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2986 }
2987 }
2988 }
2990 unshift(@new_statement_list, "VACUUM");
2992 @packages_list_statements = @new_statement_list;
2993 }
2996 sub parse_package_info {
2997 my ($baseurl, $dist, $section, $session_id)= @_;
2998 my ($package);
2999 if (not defined $session_id) { $session_id = 0; }
3000 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3001 $repo_dirs{ "${repo_path}/pool" } = 1;
3003 foreach $package ("Packages.gz"){
3004 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3005 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3006 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3007 }
3009 }
3012 sub get_package {
3013 my ($url, $dest, $session_id)= @_;
3014 if (not defined $session_id) { $session_id = 0; }
3016 my $tpath = dirname($dest);
3017 -d "$tpath" || mkpath "$tpath";
3019 # This is ugly, but I've no time to take a look at "how it works in perl"
3020 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3021 system("gunzip -cd '$dest' > '$dest.in'");
3022 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3023 unlink($dest);
3024 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3025 } else {
3026 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3027 }
3028 return 0;
3029 }
3032 sub parse_package {
3033 my ($path, $dist, $srv_path, $session_id)= @_;
3034 if (not defined $session_id) { $session_id = 0;}
3035 my ($package, $version, $section, $description);
3036 my $PACKAGES;
3037 my $timestamp = &get_time();
3039 if(not stat("$path.in")) {
3040 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3041 return;
3042 }
3044 open($PACKAGES, "<$path.in");
3045 if(not defined($PACKAGES)) {
3046 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3047 return;
3048 }
3050 # Read lines
3051 while (<$PACKAGES>){
3052 my $line = $_;
3053 # Unify
3054 chop($line);
3056 # Use empty lines as a trigger
3057 if ($line =~ /^\s*$/){
3058 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3059 push(@packages_list_statements, $sql);
3060 $package = "none";
3061 $version = "none";
3062 $section = "none";
3063 $description = "none";
3064 next;
3065 }
3067 # Trigger for package name
3068 if ($line =~ /^Package:\s/){
3069 ($package)= ($line =~ /^Package: (.*)$/);
3070 next;
3071 }
3073 # Trigger for version
3074 if ($line =~ /^Version:\s/){
3075 ($version)= ($line =~ /^Version: (.*)$/);
3076 next;
3077 }
3079 # Trigger for description
3080 if ($line =~ /^Description:\s/){
3081 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3082 next;
3083 }
3085 # Trigger for section
3086 if ($line =~ /^Section:\s/){
3087 ($section)= ($line =~ /^Section: (.*)$/);
3088 next;
3089 }
3091 # Trigger for filename
3092 if ($line =~ /^Filename:\s/){
3093 my ($filename) = ($line =~ /^Filename: (.*)$/);
3094 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3095 next;
3096 }
3097 }
3099 close( $PACKAGES );
3100 unlink( "$path.in" );
3101 }
3104 sub store_fileinfo {
3105 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3107 my %fileinfo = (
3108 'package' => $package,
3109 'dist' => $dist,
3110 'version' => $vers,
3111 );
3113 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3114 }
3117 sub cleanup_and_extract {
3118 my $fileinfo = $repo_files{ $File::Find::name };
3120 if( defined $fileinfo ) {
3121 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3122 my $sql;
3123 my $package = $fileinfo->{ 'package' };
3124 my $newver = $fileinfo->{ 'version' };
3126 mkpath($dir);
3127 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3129 if( -f "$dir/DEBIAN/templates" ) {
3131 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3133 my $tmpl= ""; {
3134 local $/=undef;
3135 open FILE, "$dir/DEBIAN/templates";
3136 $tmpl = &encode_base64(<FILE>);
3137 close FILE;
3138 }
3139 rmtree("$dir/DEBIAN/templates");
3141 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3142 push @packages_list_statements, $sql;
3143 }
3144 }
3146 return;
3147 }
3150 sub register_at_foreign_servers {
3151 my ($kernel) = $_[KERNEL];
3153 # hole alle bekannten server aus known_server_db
3154 my $server_sql = "SELECT * FROM $known_server_tn";
3155 my $server_res = $known_server_db->exec_statement($server_sql);
3157 # no entries in known_server_db
3158 if (not ref(@$server_res[0]) eq "ARRAY") {
3159 # TODO
3160 }
3162 # detect already connected clients
3163 my $client_sql = "SELECT * FROM $known_clients_tn";
3164 my $client_res = $known_clients_db->exec_statement($client_sql);
3166 # send my server details to all other gosa-si-server within the network
3167 foreach my $hit (@$server_res) {
3168 my $hostname = @$hit[0];
3169 my $hostkey = &create_passwd;
3171 # add already connected clients to registration message
3172 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3173 &add_content2xml_hash($myhash, 'key', $hostkey);
3174 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3176 # add locally loaded gosa-si modules to registration message
3177 my $loaded_modules = {};
3178 while (my ($package, $pck_info) = each %$known_modules) {
3179 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3180 foreach my $act_module (keys(%{@$pck_info[2]})) {
3181 $loaded_modules->{$act_module} = "";
3182 }
3183 }
3185 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3187 # add macaddress to registration message
3188 my ($host_ip, $host_port) = split(/:/, $hostname);
3189 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3190 my $network_interface= &get_interface_for_ip($local_ip);
3191 my $host_mac = &get_mac_for_interface($network_interface);
3192 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3194 # build registration message and send it
3195 my $foreign_server_msg = &create_xml_string($myhash);
3196 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3197 }
3199 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3200 return;
3201 }
3204 #==== MAIN = main ==============================================================
3205 # parse commandline options
3206 Getopt::Long::Configure( "bundling" );
3207 GetOptions("h|help" => \&usage,
3208 "c|config=s" => \$cfg_file,
3209 "f|foreground" => \$foreground,
3210 "v|verbose+" => \$verbose,
3211 "no-arp+" => \$no_arp,
3212 );
3214 # Prepare UID / GID as daemon_log may need it quite early
3215 $root_uid = getpwnam('root');
3216 $adm_gid = getgrnam('adm');
3218 # read and set config parameters
3219 &check_cmdline_param ;
3220 &read_configfile($cfg_file, %cfg_defaults);
3221 &check_pid;
3223 $SIG{CHLD} = 'IGNORE';
3225 # Just fork, if we are not in foreground mode
3226 if( ! $foreground ) {
3227 if (! chdir('/')) {
3228 daemon_log("Can't chdir to /: $!", 1);
3229 exit( 1 );
3230 }
3231 umask( 0 );
3232 $pid = fork;
3233 } else {
3234 $pid = $$;
3235 }
3237 if( 0 != $pid ) {
3238 # Parent: put PID into the $pid_file
3239 open( LOCK_FILE, ">$pid_file" );
3240 print LOCK_FILE "$pid\n";
3241 close( LOCK_FILE );
3242 if( !$foreground ) {
3243 exit( 0 );
3244 }
3245 }
3246 else {
3247 # Child
3248 open( STDIN, '+>/dev/null' );
3249 open( STDOUT, '+>&STDIN' );
3250 open( STDERR, '+>&STDIN' );
3251 if (! POSIX::setsid()) {
3252 daemon_log("Can't start a new session: $!");
3253 exit( 1 );
3254 }
3255 $poe_kernel->has_forked() if ($poe_kernel->can('has_forked'));
3256 }
3259 # parse head url and revision from svn
3260 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3261 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3262 $server_headURL = defined $1 ? $1 : 'unknown' ;
3263 $server_revision = defined $2 ? $2 : 'unknown' ;
3264 if ($server_headURL =~ /\/tag\// ||
3265 $server_headURL =~ /\/branches\// ) {
3266 $server_status = "stable";
3267 } else {
3268 $server_status = "developmental" ;
3269 }
3271 # Prepare log file and set permissons
3272 open(FH, ">>$log_file");
3273 close FH;
3274 chmod(0440, $log_file);
3275 chown($root_uid, $adm_gid, $log_file);
3276 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3278 daemon_log(" ", 1);
3279 daemon_log("$0 started!", 1);
3280 daemon_log("status: $server_status", 1);
3281 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3283 {
3284 no strict "refs";
3286 if ($db_module eq "DBmysql") {
3287 # connect to incoming_db
3288 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3290 # connect to gosa-si job queue
3291 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3293 # connect to known_clients_db
3294 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3296 # connect to foreign_clients_db
3297 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3299 # connect to known_server_db
3300 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3302 # connect to login_usr_db
3303 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3305 # connect to fai_server_db
3306 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3308 # connect to fai_release_db
3309 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3311 # connect to packages_list_db
3312 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3314 # connect to messaging_db
3315 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3317 } elsif ($db_module eq "DBsqlite") {
3318 # connect to incoming_db
3319 unlink($incoming_file_name);
3320 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3322 # connect to gosa-si job queue
3323 unlink($job_queue_file_name); ## just for debugging
3324 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3325 chmod(0640, $job_queue_file_name);
3326 chown($root_uid, $adm_gid, $job_queue_file_name);
3328 # connect to known_clients_db
3329 unlink($known_clients_file_name); ## just for debugging
3330 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3331 chmod(0640, $known_clients_file_name);
3332 chown($root_uid, $adm_gid, $known_clients_file_name);
3334 # connect to foreign_clients_db
3335 unlink($foreign_clients_file_name);
3336 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3337 chmod(0640, $foreign_clients_file_name);
3338 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3340 # connect to known_server_db
3341 unlink($known_server_file_name);
3342 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3343 chmod(0640, $known_server_file_name);
3344 chown($root_uid, $adm_gid, $known_server_file_name);
3346 # connect to login_usr_db
3347 unlink($login_users_file_name);
3348 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3349 chmod(0640, $login_users_file_name);
3350 chown($root_uid, $adm_gid, $login_users_file_name);
3352 # connect to fai_server_db
3353 unlink($fai_server_file_name);
3354 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3355 chmod(0640, $fai_server_file_name);
3356 chown($root_uid, $adm_gid, $fai_server_file_name);
3358 # connect to fai_release_db
3359 unlink($fai_release_file_name);
3360 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3361 chmod(0640, $fai_release_file_name);
3362 chown($root_uid, $adm_gid, $fai_release_file_name);
3364 # connect to packages_list_db
3365 #unlink($packages_list_file_name);
3366 unlink($packages_list_under_construction);
3367 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3368 chmod(0640, $packages_list_file_name);
3369 chown($root_uid, $adm_gid, $packages_list_file_name);
3371 # connect to messaging_db
3372 unlink($messaging_file_name);
3373 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3374 chmod(0640, $messaging_file_name);
3375 chown($root_uid, $adm_gid, $messaging_file_name);
3376 }
3377 }
3380 # Creating tables
3381 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3382 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3383 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3384 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3385 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3386 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3387 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3388 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3389 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3390 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3392 # create xml object used for en/decrypting
3393 $xml = new XML::Simple();
3396 # foreign servers
3397 my @foreign_server_list;
3399 # add foreign server from cfg file
3400 if ($foreign_server_string ne "") {
3401 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3402 foreach my $foreign_server (@cfg_foreign_server_list) {
3403 push(@foreign_server_list, $foreign_server);
3404 }
3406 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3407 }
3409 # Perform a DNS lookup for server registration if flag is true
3410 if ($dns_lookup eq "true") {
3411 # Add foreign server from dns
3412 my @tmp_servers;
3413 if (not $server_domain) {
3414 # Try our DNS Searchlist
3415 for my $domain(get_dns_domains()) {
3416 chomp($domain);
3417 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3418 if(@$tmp_domains) {
3419 for my $tmp_server(@$tmp_domains) {
3420 push @tmp_servers, $tmp_server;
3421 }
3422 }
3423 }
3424 if(@tmp_servers && length(@tmp_servers)==0) {
3425 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3426 }
3427 } else {
3428 @tmp_servers = &get_server_addresses($server_domain);
3429 if( 0 == @tmp_servers ) {
3430 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3431 }
3432 }
3434 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3436 foreach my $server (@tmp_servers) {
3437 unshift(@foreign_server_list, $server);
3438 }
3439 } else {
3440 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3441 }
3444 # eliminate duplicate entries
3445 @foreign_server_list = &del_doubles(@foreign_server_list);
3446 my $all_foreign_server = join(", ", @foreign_server_list);
3447 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3449 # add all found foreign servers to known_server
3450 my $cur_timestamp = &get_time();
3451 foreach my $foreign_server (@foreign_server_list) {
3453 # do not add myself to known_server_db
3454 if (&is_local($foreign_server)) { next; }
3455 ######################################
3457 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3458 primkey=>['hostname'],
3459 hostname=>$foreign_server,
3460 macaddress=>"",
3461 status=>'not_yet_registered',
3462 hostkey=>"none",
3463 loaded_modules => "none",
3464 timestamp=>$cur_timestamp,
3465 } );
3466 }
3469 # Import all modules
3470 &import_modules;
3472 # Check wether all modules are gosa-si valid passwd check
3473 &password_check;
3475 # Prepare for using Opsi
3476 if ($opsi_enabled eq "true") {
3477 use JSON::RPC::Client;
3478 use XML::Quote qw(:all);
3479 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3480 $opsi_client = new JSON::RPC::Client;
3481 }
3484 POE::Component::Server::TCP->new(
3485 Alias => "TCP_SERVER",
3486 Port => $server_port,
3487 ClientInput => sub {
3488 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3489 my $session_id = $session->ID;
3490 my $remote_ip = $heap->{'remote_ip'};
3491 push(@msgs_to_decrypt, $input);
3492 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3493 $kernel->yield("msg_to_decrypt");
3494 },
3495 InlineStates => {
3496 msg_to_decrypt => \&msg_to_decrypt,
3497 next_task => \&next_task,
3498 task_result => \&handle_task_result,
3499 task_done => \&handle_task_done,
3500 task_debug => \&handle_task_debug,
3501 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3502 }
3503 );
3505 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3507 # create session for repeatedly checking the job queue for jobs
3508 POE::Session->create(
3509 inline_states => {
3510 _start => \&session_start,
3511 _stop => \&session_stop,
3512 register_at_foreign_servers => \®ister_at_foreign_servers,
3513 next_task => \&next_task,
3514 task_result => \&handle_task_result,
3515 task_done => \&handle_task_done,
3516 task_debug => \&handle_task_debug,
3517 watch_for_next_tasks => \&watch_for_next_tasks,
3518 watch_for_new_messages => \&watch_for_new_messages,
3519 watch_for_delivery_messages => \&watch_for_delivery_messages,
3520 watch_for_done_messages => \&watch_for_done_messages,
3521 watch_for_new_jobs => \&watch_for_new_jobs,
3522 watch_for_modified_jobs => \&watch_for_modified_jobs,
3523 watch_for_done_jobs => \&watch_for_done_jobs,
3524 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3525 watch_for_old_known_clients => \&watch_for_old_known_clients,
3526 create_packages_list_db => \&run_create_packages_list_db,
3527 create_fai_server_db => \&run_create_fai_server_db,
3528 create_fai_release_db => \&run_create_fai_release_db,
3529 recreate_packages_db => \&run_recreate_packages_db,
3530 session_run_result => \&session_run_result,
3531 session_run_debug => \&session_run_debug,
3532 session_run_done => \&session_run_done,
3533 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3534 }
3535 );
3538 POE::Kernel->run();
3539 exit;