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