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