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