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