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 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file,
91 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92 $arp_activ, $gosa_unit_tag,
93 $GosaPackages_key, $gosa_timeout,
94 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96 $arp_enabled, $arp_interface,
97 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135 "timestamp VARCHAR(14) DEFAULT 'none'",
136 "headertag VARCHAR(255) DEFAULT 'none'",
137 "targettag VARCHAR(255) DEFAULT 'none'",
138 "xmlmessage TEXT",
139 "module VARCHAR(255) DEFAULT 'none'",
140 "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148 "timestamp VARCHAR(14) DEFAULT 'none'",
149 "status VARCHAR(255) DEFAULT 'none'",
150 "result TEXT",
151 "progress VARCHAR(255) DEFAULT 'none'",
152 "headertag VARCHAR(255) DEFAULT 'none'",
153 "targettag VARCHAR(255) DEFAULT 'none'",
154 "xmlmessage TEXT",
155 "macaddress VARCHAR(17) DEFAULT 'none'",
156 "plainname VARCHAR(255) DEFAULT 'none'",
157 "siserver VARCHAR(255) DEFAULT 'none'",
158 "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 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)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients";
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server";
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release";
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386";
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging";
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
208 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai";
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
225 # Lifetime of logged in user information. If no update information comes after n seconds,
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235 },
236 "server" => {
237 "ip" => [\$server_ip, "0.0.0.0"],
238 "port" => [\$server_port, "20081"],
239 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
249 "repo-path" => [\$repo_path, '/srv/www/repository'],
250 "ldap-uri" => [\$ldap_uri, ""],
251 "ldap-base" => [\$ldap_base, ""],
252 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
253 "ldap-admin-password" => [\$ldap_admin_password, ""],
254 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
255 "max-clients" => [\$max_clients, 10],
256 "wol-password" => [\$wake_on_lan_passwd, ""],
257 "mysql-username" => [\$mysql_username, "gosa_si"],
258 "mysql-password" => [\$mysql_password, ""],
259 "mysql-database" => [\$mysql_database, "gosa_si"],
260 "mysql-host" => [\$mysql_host, "127.0.0.1"],
261 },
262 "GOsaPackages" => {
263 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266 "key" => [\$GosaPackages_key, "none"],
267 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268 },
269 "ClientPackages" => {
270 "key" => [\$ClientPackages_key, "none"],
271 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272 },
273 "ServerPackages"=> {
274 "address" => [\$foreign_server_string, ""],
275 "dns-lookup" => [\$dns_lookup, "true"],
276 "domain" => [\$server_domain, ""],
277 "key" => [\$ServerPackages_key, "none"],
278 "key-lifetime" => [\$foreign_servers_register_delay, 120],
279 "job-synchronization-enabled" => [\$job_synchronization, "true"],
280 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281 },
282 "ArpHandler" => {
283 "enabled" => [\$arp_enabled, "true"],
284 "interface" => [\$arp_interface, "all"],
285 },
286 "Opsi" => {
287 "enabled" => [\$opsi_enabled, "false"],
288 "server" => [\$opsi_server, "localhost"],
289 "admin" => [\$opsi_admin, "opsi-admin"],
290 "password" => [\$opsi_password, "secret"],
291 },
293 );
296 #=== FUNCTION ================================================================
297 # NAME: usage
298 # PARAMETERS: nothing
299 # RETURNS: nothing
300 # DESCRIPTION: print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303 print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306 -h : this (help) message
307 -c <file> : config file
308 -f : foreground, process will not be forked to background
309 -v : be verbose (multiple to increase verbosity)
310 -no-arp : starts $prg without connection to arp module
312 EOF
313 print "\n" ;
314 }
317 #=== FUNCTION ================================================================
318 # NAME: logging
319 # PARAMETERS: level - string - default 'info'
320 # msg - string -
321 # facility - string - default 'LOG_DAEMON'
322 # RETURNS: nothing
323 # DESCRIPTION: function for logging
324 #===============================================================================
325 sub daemon_log {
326 # log into log_file
327 my( $msg, $level ) = @_;
328 if(not defined $msg) { return }
329 if(not defined $level) { $level = 1 }
330 if(defined $log_file){
331 open(LOG_HANDLE, ">>$log_file");
332 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333 print STDERR "cannot open $log_file: $!";
334 return
335 }
336 chomp($msg);
337 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
338 if($level <= $verbose){
339 my ($seconds, $minutes, $hours, $monthday, $month,
340 $year, $weekday, $yearday, $sommertime) = localtime(time);
341 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345 $month = $monthnames[$month];
346 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347 $year+=1900;
348 my $name = $prg;
350 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351 print LOG_HANDLE $log_msg;
352 if( $foreground ) {
353 print STDERR $log_msg;
354 }
355 }
356 close( LOG_HANDLE );
357 }
358 }
361 #=== FUNCTION ================================================================
362 # NAME: check_cmdline_param
363 # PARAMETERS: nothing
364 # RETURNS: nothing
365 # DESCRIPTION: validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368 my $err_config;
369 my $err_counter = 0;
370 if(not defined($cfg_file)) {
371 $cfg_file = "/etc/gosa-si/server.conf";
372 if(! -r $cfg_file) {
373 $err_config = "please specify a config file";
374 $err_counter += 1;
375 }
376 }
377 if( $err_counter > 0 ) {
378 &usage( "", 1 );
379 if( defined( $err_config)) { print STDERR "$err_config\n"}
380 print STDERR "\n";
381 exit( -1 );
382 }
383 }
386 #=== FUNCTION ================================================================
387 # NAME: check_pid
388 # PARAMETERS: nothing
389 # RETURNS: nothing
390 # DESCRIPTION: handels pid processing
391 #===============================================================================
392 sub check_pid {
393 $pid = -1;
394 # Check, if we are already running
395 if( open(LOCK_FILE, "<$pid_file") ) {
396 $pid = <LOCK_FILE>;
397 if( defined $pid ) {
398 chomp( $pid );
399 if( -f "/proc/$pid/stat" ) {
400 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401 if( $stat ) {
402 daemon_log("ERROR: Already running",1);
403 close( LOCK_FILE );
404 exit -1;
405 }
406 }
407 }
408 close( LOCK_FILE );
409 unlink( $pid_file );
410 }
412 # create a syslog msg if it is not to possible to open PID file
413 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415 if (open(LOCK_FILE, '<', $pid_file)
416 && ($pid = <LOCK_FILE>))
417 {
418 chomp($pid);
419 $msg .= "(PID $pid)\n";
420 } else {
421 $msg .= "(unable to read PID)\n";
422 }
423 if( ! ($foreground) ) {
424 openlog( $0, "cons,pid", "daemon" );
425 syslog( "warning", $msg );
426 closelog();
427 }
428 else {
429 print( STDERR " $msg " );
430 }
431 exit( -1 );
432 }
433 }
435 #=== FUNCTION ================================================================
436 # NAME: import_modules
437 # PARAMETERS: module_path - string - abs. path to the directory the modules
438 # are stored
439 # RETURNS: nothing
440 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
441 # state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444 daemon_log(" ", 1);
446 if (not -e $modules_path) {
447 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
448 }
450 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451 while (defined (my $file = readdir (DIR))) {
452 if (not $file =~ /(\S*?).pm$/) {
453 next;
454 }
455 my $mod_name = $1;
457 # ArpHandler switch
458 if( $file =~ /ArpHandler.pm/ ) {
459 if( $arp_enabled eq "false" ) { next; }
460 }
462 eval { require $file; };
463 if ($@) {
464 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465 daemon_log("$@", 1);
466 exit;
467 } else {
468 my $info = eval($mod_name.'::get_module_info()');
469 # Only load module if get_module_info() returns a non-null object
470 if( $info ) {
471 my ($input_address, $input_key, $event_hash) = @{$info};
472 $known_modules->{$mod_name} = $info;
473 daemon_log("0 INFO: module $mod_name loaded", 5);
474 }
475 }
476 }
478 close (DIR);
479 }
481 #=== FUNCTION ================================================================
482 # NAME: password_check
483 # PARAMETERS: nothing
484 # RETURNS: nothing
485 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
486 # the same password
487 #===============================================================================
488 sub password_check {
489 my $passwd_hash = {};
490 while (my ($mod_name, $mod_info) = each %$known_modules) {
491 my $mod_passwd = @$mod_info[1];
492 if (not defined $mod_passwd) { next; }
493 if (not exists $passwd_hash->{$mod_passwd}) {
494 $passwd_hash->{$mod_passwd} = $mod_name;
496 # escalates critical error
497 } else {
498 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500 exit( -1 );
501 }
502 }
504 }
507 #=== FUNCTION ================================================================
508 # NAME: sig_int_handler
509 # PARAMETERS: signal - string - signal arose from system
510 # RETURNS: nothing
511 # DESCRIPTION: handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514 my ($signal) = @_;
516 # if (defined($ldap_handle)) {
517 # $ldap_handle->disconnect;
518 # }
519 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
522 daemon_log("shutting down gosa-si-server", 1);
523 system("kill `ps -C gosa-si-server -o pid=`");
524 }
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529 my ($crypted_msg, $module_key, $session_id) = @_;
530 my $msg;
531 my $msg_hash;
532 my $error_string;
533 eval{
534 $msg = &decrypt_msg($crypted_msg, $module_key);
536 if ($msg =~ /<xml>/i){
537 $msg =~ s/\s+/ /g; # just for better daemon_log
538 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541 ##############
542 # check header
543 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544 my $header_l = $msg_hash->{'header'};
545 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
546 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547 my $header = @{$header_l}[0];
548 if( 0 == length $header) { die 'empty string in header tag'; }
550 ##############
551 # check source
552 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553 my $source_l = $msg_hash->{'source'};
554 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
555 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556 my $source = @{$source_l}[0];
557 if( 0 == length $source) { die 'source error'; }
559 ##############
560 # check target
561 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562 my $target_l = $msg_hash->{'target'};
563 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
564 }
565 };
566 if($@) {
567 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568 $msg = undef;
569 $msg_hash = undef;
570 }
572 return ($msg, $msg_hash);
573 }
576 sub check_outgoing_xml_validity {
577 my ($msg, $session_id) = @_;
579 my $msg_hash;
580 eval{
581 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583 ##############
584 # check header
585 my $header_l = $msg_hash->{'header'};
586 if( 1 != @{$header_l} ) {
587 die 'no or more than one headers specified';
588 }
589 my $header = @{$header_l}[0];
590 if( 0 == length $header) {
591 die 'header has length 0';
592 }
594 ##############
595 # check source
596 my $source_l = $msg_hash->{'source'};
597 if( 1 != @{$source_l} ) {
598 die 'no or more than 1 sources specified';
599 }
600 my $source = @{$source_l}[0];
601 if( 0 == length $source) {
602 die 'source has length 0';
603 }
605 # Check if source contains hostname instead of ip address
606 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
607 my ($hostname,$port) = split(/:/, $source);
608 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
609 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
610 # Write ip address to $source variable
611 $source = "$ip_address:$port";
612 }
613 }
614 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
615 $source =~ /^GOSA$/i) {
616 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
617 }
619 ##############
620 # check target
621 my $target_l = $msg_hash->{'target'};
622 if( 0 == @{$target_l} ) {
623 die "no targets specified";
624 }
625 foreach my $target (@$target_l) {
626 if( 0 == length $target) {
627 die "target has length 0";
628 }
629 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
630 $target =~ /^GOSA$/i ||
631 $target =~ /^\*$/ ||
632 $target =~ /KNOWN_SERVER/i ||
633 $target =~ /JOBDB/i ||
634 $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 ){
635 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
636 }
637 }
638 };
639 if($@) {
640 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
641 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
642 $msg_hash = undef;
643 }
645 return ($msg_hash);
646 }
649 sub input_from_known_server {
650 my ($input, $remote_ip, $session_id) = @_ ;
651 my ($msg, $msg_hash, $module);
653 my $sql_statement= "SELECT * FROM known_server";
654 my $query_res = $known_server_db->select_dbentry( $sql_statement );
656 while( my ($hit_num, $hit) = each %{ $query_res } ) {
657 my $host_name = $hit->{hostname};
658 if( not $host_name =~ "^$remote_ip") {
659 next;
660 }
661 my $host_key = $hit->{hostkey};
662 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
663 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
665 # check if module can open msg envelope with module key
666 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
667 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
668 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
669 daemon_log("$@", 8);
670 next;
671 }
672 else {
673 $msg = $tmp_msg;
674 $msg_hash = $tmp_msg_hash;
675 $module = "ServerPackages";
676 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
677 last;
678 }
679 }
681 if( (!$msg) || (!$msg_hash) || (!$module) ) {
682 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
683 }
685 return ($msg, $msg_hash, $module);
686 }
689 sub input_from_known_client {
690 my ($input, $remote_ip, $session_id) = @_ ;
691 my ($msg, $msg_hash, $module);
693 my $sql_statement= "SELECT * FROM known_clients";
694 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
695 while( my ($hit_num, $hit) = each %{ $query_res } ) {
696 my $host_name = $hit->{hostname};
697 if( not $host_name =~ /^$remote_ip:\d*$/) {
698 next;
699 }
700 my $host_key = $hit->{hostkey};
701 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
702 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
704 # check if module can open msg envelope with module key
705 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
707 if( (!$msg) || (!$msg_hash) ) {
708 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
709 &daemon_log("$@", 8);
710 next;
711 }
712 else {
713 $module = "ClientPackages";
714 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
715 last;
716 }
717 }
719 if( (!$msg) || (!$msg_hash) || (!$module) ) {
720 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
721 }
723 return ($msg, $msg_hash, $module);
724 }
727 sub input_from_unknown_host {
728 no strict "refs";
729 my ($input, $session_id) = @_ ;
730 my ($msg, $msg_hash, $module);
731 my $error_string;
733 my %act_modules = %$known_modules;
735 while( my ($mod, $info) = each(%act_modules)) {
737 # check a key exists for this module
738 my $module_key = ${$mod."_key"};
739 if( not defined $module_key ) {
740 if( $mod eq 'ArpHandler' ) {
741 next;
742 }
743 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
744 next;
745 }
746 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
748 # check if module can open msg envelope with module key
749 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
750 if( (not defined $msg) || (not defined $msg_hash) ) {
751 next;
752 } else {
753 $module = $mod;
754 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
755 last;
756 }
757 }
759 if( (!$msg) || (!$msg_hash) || (!$module)) {
760 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
761 }
763 return ($msg, $msg_hash, $module);
764 }
767 sub create_ciphering {
768 my ($passwd) = @_;
769 if((!defined($passwd)) || length($passwd)==0) {
770 $passwd = "";
771 }
772 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
773 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
774 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
775 $my_cipher->set_iv($iv);
776 return $my_cipher;
777 }
780 sub encrypt_msg {
781 my ($msg, $key) = @_;
782 my $my_cipher = &create_ciphering($key);
783 my $len;
784 {
785 use bytes;
786 $len= 16-length($msg)%16;
787 }
788 $msg = "\0"x($len).$msg;
789 $msg = $my_cipher->encrypt($msg);
790 chomp($msg = &encode_base64($msg));
791 # there are no newlines allowed inside msg
792 $msg=~ s/\n//g;
793 return $msg;
794 }
797 sub decrypt_msg {
799 my ($msg, $key) = @_ ;
800 $msg = &decode_base64($msg);
801 my $my_cipher = &create_ciphering($key);
802 $msg = $my_cipher->decrypt($msg);
803 $msg =~ s/\0*//g;
804 return $msg;
805 }
808 sub get_encrypt_key {
809 my ($target) = @_ ;
810 my $encrypt_key;
811 my $error = 0;
813 # target can be in known_server
814 if( not defined $encrypt_key ) {
815 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
816 my $query_res = $known_server_db->select_dbentry( $sql_statement );
817 while( my ($hit_num, $hit) = each %{ $query_res } ) {
818 my $host_name = $hit->{hostname};
819 if( $host_name ne $target ) {
820 next;
821 }
822 $encrypt_key = $hit->{hostkey};
823 last;
824 }
825 }
827 # target can be in known_client
828 if( not defined $encrypt_key ) {
829 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
830 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
831 while( my ($hit_num, $hit) = each %{ $query_res } ) {
832 my $host_name = $hit->{hostname};
833 if( $host_name ne $target ) {
834 next;
835 }
836 $encrypt_key = $hit->{hostkey};
837 last;
838 }
839 }
841 return $encrypt_key;
842 }
845 #=== FUNCTION ================================================================
846 # NAME: open_socket
847 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
848 # [PeerPort] string necessary if port not appended by PeerAddr
849 # RETURNS: socket IO::Socket::INET
850 # DESCRIPTION: open a socket to PeerAddr
851 #===============================================================================
852 sub open_socket {
853 my ($PeerAddr, $PeerPort) = @_ ;
854 if(defined($PeerPort)){
855 $PeerAddr = $PeerAddr.":".$PeerPort;
856 }
857 my $socket;
858 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
859 Porto => "tcp",
860 Type => SOCK_STREAM,
861 Timeout => 5,
862 );
863 if(not defined $socket) {
864 return;
865 }
866 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
867 return $socket;
868 }
871 #sub get_local_ip_for_remote_ip {
872 # my $remote_ip= shift;
873 # my $result="0.0.0.0";
874 #
875 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
876 # if($remote_ip eq "127.0.0.1") {
877 # $result = "127.0.0.1";
878 # } else {
879 # my $PROC_NET_ROUTE= ('/proc/net/route');
880 #
881 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
882 # or die "Could not open $PROC_NET_ROUTE";
883 #
884 # my @ifs = <PROC_NET_ROUTE>;
885 #
886 # close(PROC_NET_ROUTE);
887 #
888 # # Eat header line
889 # shift @ifs;
890 # chomp @ifs;
891 # foreach my $line(@ifs) {
892 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
893 # my $destination;
894 # my $mask;
895 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
896 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
897 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
898 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
899 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
900 # # destination matches route, save mac and exit
901 # $result= &get_ip($Iface);
902 # last;
903 # }
904 # }
905 # }
906 # } else {
907 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
908 # }
909 # return $result;
910 #}
913 sub send_msg_to_target {
914 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
915 my $error = 0;
916 my $header;
917 my $timestamp = &get_time();
918 my $new_status;
919 my $act_status;
920 my ($sql_statement, $res);
922 if( $msg_header ) {
923 $header = "'$msg_header'-";
924 } else {
925 $header = "";
926 }
928 # Patch the source ip
929 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
930 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
931 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
932 }
934 # encrypt xml msg
935 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
937 # opensocket
938 my $socket = &open_socket($address);
939 if( !$socket ) {
940 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
941 $error++;
942 }
944 if( $error == 0 ) {
945 # send xml msg
946 print $socket $crypted_msg."\n";
948 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
949 daemon_log("$session_id DEBUG: message:\n$msg", 9);
951 }
953 # close socket in any case
954 if( $socket ) {
955 close $socket;
956 }
958 if( $error > 0 ) { $new_status = "down"; }
959 else { $new_status = $msg_header; }
962 # known_clients
963 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
964 $res = $known_clients_db->select_dbentry($sql_statement);
965 if( keys(%$res) == 1) {
966 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967 if ($act_status eq "down" && $new_status eq "down") {
968 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
969 $res = $known_clients_db->del_dbentry($sql_statement);
970 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
971 } else {
972 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_clients_db->update_dbentry($sql_statement);
974 if($new_status eq "down"){
975 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976 } else {
977 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978 }
979 }
980 }
982 # known_server
983 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
984 $res = $known_server_db->select_dbentry($sql_statement);
985 if( keys(%$res) == 1) {
986 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
987 if ($act_status eq "down" && $new_status eq "down") {
988 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
989 $res = $known_server_db->del_dbentry($sql_statement);
990 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
991 }
992 else {
993 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
994 $res = $known_server_db->update_dbentry($sql_statement);
995 if($new_status eq "down"){
996 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
997 } else {
998 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
999 }
1000 }
1001 }
1002 return $error;
1003 }
1006 sub update_jobdb_status_for_send_msgs {
1007 my ($answer, $error) = @_;
1008 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1009 my $jobdb_id = $1;
1011 # sending msg faild
1012 if( $error ) {
1014 daemon_log("D \n$error\n$answer");
1016 if ((not $answer =~ /<header>trigger_action_reinstall<\/header>/) || (not $answer =~ /<header>trigger_action_update<\/header>/)) {
1017 my $sql_statement = "UPDATE $job_queue_tn ".
1018 "SET status='error', result='can not deliver msg, please consult log file' ".
1019 "WHERE id=$jobdb_id";
1020 my $res = $job_db->update_dbentry($sql_statement);
1021 }
1023 # sending msg was successful
1024 } else {
1025 my $sql_statement = "UPDATE $job_queue_tn ".
1026 "SET status='done' ".
1027 "WHERE id=$jobdb_id AND status='processed'";
1028 my $res = $job_db->update_dbentry($sql_statement);
1029 }
1030 }
1031 }
1034 sub sig_handler {
1035 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1036 daemon_log("0 INFO got signal '$signal'", 1);
1037 $kernel->sig_handled();
1038 return;
1039 }
1042 sub msg_to_decrypt {
1043 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1044 my $session_id = $session->ID;
1045 my ($msg, $msg_hash, $module);
1046 my $error = 0;
1048 # hole neue msg aus @msgs_to_decrypt
1049 my $next_msg = shift @msgs_to_decrypt;
1051 # msg is from a new client or gosa
1052 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1054 # msg is from a gosa-si-server
1055 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1056 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1057 }
1058 # msg is from a gosa-si-client
1059 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1060 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1061 }
1062 # an error occurred
1063 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1064 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1065 # could not understand a msg from its server the client cause a re-registering process
1066 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1067 "' to cause a re-registering of the client if necessary", 3);
1068 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1069 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1070 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1071 my $host_name = $hit->{'hostname'};
1072 my $host_key = $hit->{'hostkey'};
1073 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1074 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1075 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1076 }
1077 $error++;
1078 }
1081 my $header;
1082 my $target;
1083 my $source;
1084 my $done = 0;
1085 my $sql;
1086 my $res;
1088 # check whether this message should be processed here
1089 if ($error == 0) {
1090 $header = @{$msg_hash->{'header'}}[0];
1091 $target = @{$msg_hash->{'target'}}[0];
1092 $source = @{$msg_hash->{'source'}}[0];
1093 my $not_found_in_known_clients_db = 0;
1094 my $not_found_in_known_server_db = 0;
1095 my $not_found_in_foreign_clients_db = 0;
1096 my $local_address;
1097 my $local_mac;
1098 my ($target_ip, $target_port) = split(':', $target);
1100 # Determine the local ip address if target is an ip address
1101 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1102 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1103 } else {
1104 $local_address = $server_address;
1105 }
1107 # Determine the local mac address if target is a mac address
1108 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) {
1109 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1110 my $network_interface= &get_interface_for_ip($loc_ip);
1111 $local_mac = &get_mac_for_interface($network_interface);
1112 } else {
1113 $local_mac = $server_mac_address;
1114 }
1116 # target and source is equal to GOSA -> process here
1117 if (not $done) {
1118 if ($target eq "GOSA" && $source eq "GOSA") {
1119 $done = 1;
1120 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1121 }
1122 }
1124 # target is own address without forward_to_gosa-tag -> process here
1125 if (not $done) {
1126 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1127 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1128 $done = 1;
1129 if ($source eq "GOSA") {
1130 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1131 }
1132 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1133 }
1134 }
1136 # target is a client address in known_clients -> process here
1137 if (not $done) {
1138 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1139 $res = $known_clients_db->select_dbentry($sql);
1140 if (keys(%$res) > 0) {
1141 $done = 1;
1142 my $hostname = $res->{1}->{'hostname'};
1143 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1144 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1145 if ($source eq "GOSA") {
1146 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1147 }
1148 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1150 } else {
1151 $not_found_in_known_clients_db = 1;
1152 }
1153 }
1155 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1156 if (not $done) {
1157 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1158 my $gosa_at;
1159 my $gosa_session_id;
1160 if (($target eq $local_address) && (defined $forward_to_gosa)){
1161 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1162 if ($gosa_at ne $local_address) {
1163 $done = 1;
1164 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1165 }
1166 }
1167 }
1169 # if message should be processed here -> add message to incoming_db
1170 if ($done) {
1171 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1172 # so gosa-si-server knows how to process this kind of messages
1173 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1174 $module = "GosaPackages";
1175 }
1177 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1178 primkey=>[],
1179 headertag=>$header,
1180 targettag=>$target,
1181 xmlmessage=>&encode_base64($msg),
1182 timestamp=>&get_time,
1183 module=>$module,
1184 sessionid=>$session_id,
1185 } );
1187 }
1189 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1190 if (not $done) {
1191 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1192 my $gosa_at;
1193 my $gosa_session_id;
1194 if (($target eq $local_address) && (defined $forward_to_gosa)){
1195 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1196 if ($gosa_at eq $local_address) {
1197 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1198 if( defined $session_reference ) {
1199 $heap = $session_reference->get_heap();
1200 }
1201 if(exists $heap->{'client'}) {
1202 $msg = &encrypt_msg($msg, $GosaPackages_key);
1203 $heap->{'client'}->put($msg);
1204 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1205 }
1206 $done = 1;
1207 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1208 }
1209 }
1211 }
1213 # target is a client address in foreign_clients -> forward to registration server
1214 if (not $done) {
1215 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1216 $res = $foreign_clients_db->select_dbentry($sql);
1217 if (keys(%$res) > 0) {
1218 my $hostname = $res->{1}->{'hostname'};
1219 my ($host_ip, $host_port) = split(/:/, $hostname);
1220 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1221 my $regserver = $res->{1}->{'regserver'};
1222 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1223 my $res = $known_server_db->select_dbentry($sql);
1224 if (keys(%$res) > 0) {
1225 my $regserver_key = $res->{1}->{'hostkey'};
1226 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1227 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1228 if ($source eq "GOSA") {
1229 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1230 }
1231 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1232 }
1233 $done = 1;
1234 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1235 } else {
1236 $not_found_in_foreign_clients_db = 1;
1237 }
1238 }
1240 # target is a server address -> forward to server
1241 if (not $done) {
1242 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1243 $res = $known_server_db->select_dbentry($sql);
1244 if (keys(%$res) > 0) {
1245 my $hostkey = $res->{1}->{'hostkey'};
1247 if ($source eq "GOSA") {
1248 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1249 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1251 }
1253 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1254 $done = 1;
1255 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1256 } else {
1257 $not_found_in_known_server_db = 1;
1258 }
1259 }
1262 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1263 if ( $not_found_in_foreign_clients_db
1264 && $not_found_in_known_server_db
1265 && $not_found_in_known_clients_db) {
1266 &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);
1267 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1268 $module = "GosaPackages";
1269 }
1270 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1271 primkey=>[],
1272 headertag=>$header,
1273 targettag=>$target,
1274 xmlmessage=>&encode_base64($msg),
1275 timestamp=>&get_time,
1276 module=>$module,
1277 sessionid=>$session_id,
1278 } );
1279 $done = 1;
1280 }
1283 if (not $done) {
1284 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1285 if ($source eq "GOSA") {
1286 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1287 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1289 my $session_reference = $kernel->ID_id_to_session($session_id);
1290 if( defined $session_reference ) {
1291 $heap = $session_reference->get_heap();
1292 }
1293 if(exists $heap->{'client'}) {
1294 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1295 $heap->{'client'}->put($error_msg);
1296 }
1297 }
1298 }
1300 }
1302 return;
1303 }
1306 sub next_task {
1307 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1308 my $running_task = POE::Wheel::Run->new(
1309 Program => sub { process_task($session, $heap, $task) },
1310 StdioFilter => POE::Filter::Reference->new(),
1311 StdoutEvent => "task_result",
1312 StderrEvent => "task_debug",
1313 CloseEvent => "task_done",
1314 );
1315 $heap->{task}->{ $running_task->ID } = $running_task;
1316 }
1318 sub handle_task_result {
1319 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1320 my $client_answer = $result->{'answer'};
1321 if( $client_answer =~ s/session_id=(\d+)$// ) {
1322 my $session_id = $1;
1323 if( defined $session_id ) {
1324 my $session_reference = $kernel->ID_id_to_session($session_id);
1325 if( defined $session_reference ) {
1326 $heap = $session_reference->get_heap();
1327 }
1328 }
1330 if(exists $heap->{'client'}) {
1331 $heap->{'client'}->put($client_answer);
1332 }
1333 }
1334 $kernel->sig(CHLD => "child_reap");
1335 }
1337 sub handle_task_debug {
1338 my $result = $_[ARG0];
1339 print STDERR "$result\n";
1340 }
1342 sub handle_task_done {
1343 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1344 delete $heap->{task}->{$task_id};
1345 }
1347 sub process_task {
1348 no strict "refs";
1349 #CHECK: Not @_[...]?
1350 my ($session, $heap, $task) = @_;
1351 my $error = 0;
1352 my $answer_l;
1353 my ($answer_header, @answer_target_l, $answer_source);
1354 my $client_answer = "";
1356 # prepare all variables needed to process message
1357 #my $msg = $task->{'xmlmessage'};
1358 my $msg = &decode_base64($task->{'xmlmessage'});
1359 my $incoming_id = $task->{'id'};
1360 my $module = $task->{'module'};
1361 my $header = $task->{'headertag'};
1362 my $session_id = $task->{'sessionid'};
1363 my $msg_hash;
1364 eval {
1365 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1366 };
1367 daemon_log("ERROR: XML failure '$@'") if ($@);
1368 my $source = @{$msg_hash->{'source'}}[0];
1370 # set timestamp of incoming client uptodate, so client will not
1371 # be deleted from known_clients because of expiration
1372 my $act_time = &get_time();
1373 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1374 my $res = $known_clients_db->exec_statement($sql);
1376 ######################
1377 # process incoming msg
1378 if( $error == 0) {
1379 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1380 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1381 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1383 if ( 0 < @{$answer_l} ) {
1384 my $answer_str = join("\n", @{$answer_l});
1385 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1386 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1387 }
1388 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1389 } else {
1390 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1391 }
1393 }
1394 if( !$answer_l ) { $error++ };
1396 ########
1397 # answer
1398 if( $error == 0 ) {
1400 foreach my $answer ( @{$answer_l} ) {
1401 # check outgoing msg to xml validity
1402 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1403 if( not defined $answer_hash ) { next; }
1405 $answer_header = @{$answer_hash->{'header'}}[0];
1406 @answer_target_l = @{$answer_hash->{'target'}};
1407 $answer_source = @{$answer_hash->{'source'}}[0];
1409 # deliver msg to all targets
1410 foreach my $answer_target ( @answer_target_l ) {
1412 # targets of msg are all gosa-si-clients in known_clients_db
1413 if( $answer_target eq "*" ) {
1414 # answer is for all clients
1415 my $sql_statement= "SELECT * FROM known_clients";
1416 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1417 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1418 my $host_name = $hit->{hostname};
1419 my $host_key = $hit->{hostkey};
1420 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1421 &update_jobdb_status_for_send_msgs($answer, $error);
1422 }
1423 }
1425 # targets of msg are all gosa-si-server in known_server_db
1426 elsif( $answer_target eq "KNOWN_SERVER" ) {
1427 # answer is for all server in known_server
1428 my $sql_statement= "SELECT * FROM $known_server_tn";
1429 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1430 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1431 my $host_name = $hit->{hostname};
1432 my $host_key = $hit->{hostkey};
1433 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1434 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1435 &update_jobdb_status_for_send_msgs($answer, $error);
1436 }
1437 }
1439 # target of msg is GOsa
1440 elsif( $answer_target eq "GOSA" ) {
1441 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1442 my $add_on = "";
1443 if( defined $session_id ) {
1444 $add_on = ".session_id=$session_id";
1445 }
1446 # answer is for GOSA and has to returned to connected client
1447 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1448 $client_answer = $gosa_answer.$add_on;
1449 }
1451 # target of msg is job queue at this host
1452 elsif( $answer_target eq "JOBDB") {
1453 $answer =~ /<header>(\S+)<\/header>/;
1454 my $header;
1455 if( defined $1 ) { $header = $1; }
1456 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1457 &update_jobdb_status_for_send_msgs($answer, $error);
1458 }
1460 # Target of msg is a mac address
1461 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 ) {
1462 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1464 # Looking for macaddress in known_clients
1465 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1466 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1467 my $found_ip_flag = 0;
1468 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1469 my $host_name = $hit->{hostname};
1470 my $host_key = $hit->{hostkey};
1471 $answer =~ s/$answer_target/$host_name/g;
1472 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1473 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1474 &update_jobdb_status_for_send_msgs($answer, $error);
1475 $found_ip_flag++ ;
1476 }
1478 # Looking for macaddress in foreign_clients
1479 if ($found_ip_flag == 0) {
1480 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1481 my $res = $foreign_clients_db->select_dbentry($sql);
1482 while( my ($hit_num, $hit) = each %{ $res } ) {
1483 my $host_name = $hit->{hostname};
1484 my $reg_server = $hit->{regserver};
1485 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1487 # Fetch key for reg_server
1488 my $reg_server_key;
1489 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1490 my $res = $known_server_db->select_dbentry($sql);
1491 if (exists $res->{1}) {
1492 $reg_server_key = $res->{1}->{'hostkey'};
1493 } else {
1494 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1495 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1496 $reg_server_key = undef;
1497 }
1499 # Send answer to server where client is registered
1500 if (defined $reg_server_key) {
1501 $answer =~ s/$answer_target/$host_name/g;
1502 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1503 &update_jobdb_status_for_send_msgs($answer, $error);
1504 $found_ip_flag++ ;
1505 }
1506 }
1507 }
1509 # No mac to ip matching found
1510 if( $found_ip_flag == 0) {
1511 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1513 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of 30 sec
1514 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", 30);
1515 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$answer_target' AND headertag='$answer_header')";
1516 my $res = $job_db->update_dbentry($sql);
1517 daemon_log("$session_id INFO: '$answer_header'-job will be reactivated at '$delay_timestamp' ".
1518 "cause client '$answer_target' is currently not available", 5);
1519 daemon_log("$session_id $sql", 7);
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($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='".&get_time(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/) || ($header =~ /gosa_trigger_action_update/)) {
1815 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1816 my $dbres = $job_db->update_dbentry($sql_statement);
1817 }
1819 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1820 else {
1821 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1822 my $dbres = $job_db->update_dbentry($sql_statement);
1823 }
1826 # We don't want parallel processing
1827 last;
1828 }
1829 }
1830 }
1832 $watch_for_new_jobs_in_progress = 0;
1833 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1834 }
1835 }
1838 sub watch_for_new_messages {
1839 my ($kernel,$heap) = @_[KERNEL, HEAP];
1840 my @coll_user_msg; # collection list of outgoing messages
1842 # check messaging_db for new incoming messages with executable timestamp
1843 my $timestamp = &get_time();
1844 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1845 my $res = $messaging_db->exec_statement( $sql_statement );
1846 foreach my $hit (@{$res}) {
1848 # create outgoing messages
1849 my $message_to = @{$hit}[3];
1850 # translate message_to to plain login name
1851 my @message_to_l = split(/,/, $message_to);
1852 my %receiver_h;
1853 foreach my $receiver (@message_to_l) {
1854 if ($receiver =~ /^u_([\s\S]*)$/) {
1855 $receiver_h{$receiver} = 0;
1856 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1857 my $group_name = $1;
1858 # fetch all group members from ldap and add them to receiver hash
1859 my $ldap_handle = &get_ldap_handle();
1860 if (defined $ldap_handle) {
1861 my $mesg = $ldap_handle->search(
1862 base => $ldap_base,
1863 scope => 'sub',
1864 attrs => ['memberUid'],
1865 filter => "cn=$group_name",
1866 );
1867 if ($mesg->count) {
1868 my @entries = $mesg->entries;
1869 foreach my $entry (@entries) {
1870 my @receivers= $entry->get_value("memberUid");
1871 foreach my $receiver (@receivers) {
1872 $receiver_h{$receiver} = 0;
1873 }
1874 }
1875 }
1876 # translating errors ?
1877 if ($mesg->code) {
1878 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1879 }
1880 # ldap handle error ?
1881 } else {
1882 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1883 }
1884 } else {
1885 my $sbjct = &encode_base64(@{$hit}[1]);
1886 my $msg = &encode_base64(@{$hit}[7]);
1887 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1888 }
1889 }
1890 my @receiver_l = keys(%receiver_h);
1892 my $message_id = @{$hit}[0];
1894 #add each outgoing msg to messaging_db
1895 my $receiver;
1896 foreach $receiver (@receiver_l) {
1897 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1898 "VALUES ('".
1899 $message_id."', '". # id
1900 @{$hit}[1]."', '". # subject
1901 @{$hit}[2]."', '". # message_from
1902 $receiver."', '". # message_to
1903 "none"."', '". # flag
1904 "out"."', '". # direction
1905 @{$hit}[6]."', '". # delivery_time
1906 @{$hit}[7]."', '". # message
1907 $timestamp."'". # timestamp
1908 ")";
1909 &daemon_log("M DEBUG: $sql_statement", 1);
1910 my $res = $messaging_db->exec_statement($sql_statement);
1911 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1912 }
1914 # set incoming message to flag d=deliverd
1915 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1916 &daemon_log("M DEBUG: $sql_statement", 7);
1917 $res = $messaging_db->update_dbentry($sql_statement);
1918 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1919 }
1921 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1922 return;
1923 }
1925 sub watch_for_delivery_messages {
1926 my ($kernel, $heap) = @_[KERNEL, HEAP];
1928 # select outgoing messages
1929 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1930 #&daemon_log("0 DEBUG: $sql", 7);
1931 my $res = $messaging_db->exec_statement( $sql_statement );
1933 # build out msg for each usr
1934 foreach my $hit (@{$res}) {
1935 my $receiver = @{$hit}[3];
1936 my $msg_id = @{$hit}[0];
1937 my $subject = @{$hit}[1];
1938 my $message = @{$hit}[7];
1940 # resolve usr -> host where usr is logged in
1941 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1942 #&daemon_log("0 DEBUG: $sql", 7);
1943 my $res = $login_users_db->exec_statement($sql);
1945 # receiver is logged in nowhere
1946 if (not ref(@$res[0]) eq "ARRAY") { next; }
1948 # receiver ist logged in at a client registered at local server
1949 my $send_succeed = 0;
1950 foreach my $hit (@$res) {
1951 my $receiver_host = @$hit[0];
1952 my $delivered2host = 0;
1953 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1955 # Looking for host in know_clients_db
1956 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1957 my $res = $known_clients_db->exec_statement($sql);
1959 # Host is known in known_clients_db
1960 if (ref(@$res[0]) eq "ARRAY") {
1961 my $receiver_key = @{@{$res}[0]}[2];
1962 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1963 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1964 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1965 if ($error == 0 ) {
1966 $send_succeed++ ;
1967 $delivered2host++ ;
1968 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1969 } else {
1970 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1971 }
1972 }
1974 # Message already send, do not need to do anything more, otherwise ...
1975 if ($delivered2host) { next;}
1977 # ...looking for host in foreign_clients_db
1978 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1979 $res = $foreign_clients_db->exec_statement($sql);
1981 # Host is known in foreign_clients_db
1982 if (ref(@$res[0]) eq "ARRAY") {
1983 my $registration_server = @{@{$res}[0]}[2];
1985 # Fetch encryption key for registration server
1986 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1987 my $res = $known_server_db->exec_statement($sql);
1988 if (ref(@$res[0]) eq "ARRAY") {
1989 my $registration_server_key = @{@{$res}[0]}[3];
1990 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1991 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1992 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1993 if ($error == 0 ) {
1994 $send_succeed++ ;
1995 $delivered2host++ ;
1996 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1997 } else {
1998 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1999 }
2001 } else {
2002 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2003 "registrated at server '$registration_server', ".
2004 "but no data available in known_server_db ", 1);
2005 }
2006 }
2008 if (not $delivered2host) {
2009 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2010 }
2011 }
2013 if ($send_succeed) {
2014 # set outgoing msg at db to deliverd
2015 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2016 my $res = $messaging_db->exec_statement($sql);
2017 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2018 } else {
2019 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2020 }
2021 }
2023 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2024 return;
2025 }
2028 sub watch_for_done_messages {
2029 my ($kernel,$heap) = @_[KERNEL, HEAP];
2031 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2032 #&daemon_log("0 DEBUG: $sql", 7);
2033 my $res = $messaging_db->exec_statement($sql);
2035 foreach my $hit (@{$res}) {
2036 my $msg_id = @{$hit}[0];
2038 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2039 #&daemon_log("0 DEBUG: $sql", 7);
2040 my $res = $messaging_db->exec_statement($sql);
2042 # not all usr msgs have been seen till now
2043 if ( ref(@$res[0]) eq "ARRAY") { next; }
2045 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2046 #&daemon_log("0 DEBUG: $sql", 7);
2047 $res = $messaging_db->exec_statement($sql);
2049 }
2051 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2052 return;
2053 }
2056 sub watch_for_old_known_clients {
2057 my ($kernel,$heap) = @_[KERNEL, HEAP];
2059 my $sql_statement = "SELECT * FROM $known_clients_tn";
2060 my $res = $known_clients_db->select_dbentry( $sql_statement );
2062 my $act_time = int(&get_time());
2064 while ( my ($hit_num, $hit) = each %$res) {
2065 my $expired_timestamp = int($hit->{'timestamp'});
2066 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2067 my $dt = DateTime->new( year => $1,
2068 month => $2,
2069 day => $3,
2070 hour => $4,
2071 minute => $5,
2072 second => $6,
2073 );
2075 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2076 $expired_timestamp = $dt->ymd('').$dt->hms('');
2077 if ($act_time > $expired_timestamp) {
2078 my $hostname = $hit->{'hostname'};
2079 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2080 my $del_res = $known_clients_db->exec_statement($del_sql);
2082 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2083 }
2085 }
2087 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2088 }
2091 sub watch_for_next_tasks {
2092 my ($kernel,$heap) = @_[KERNEL, HEAP];
2094 my $sql = "SELECT * FROM $incoming_tn";
2095 my $res = $incoming_db->select_dbentry($sql);
2097 while ( my ($hit_num, $hit) = each %$res) {
2098 my $headertag = $hit->{'headertag'};
2099 if ($headertag =~ /^answer_(\d+)/) {
2100 # do not start processing, this message is for a still running POE::Wheel
2101 next;
2102 }
2103 my $message_id = $hit->{'id'};
2104 my $session_id = $hit->{'sessionid'};
2105 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2106 $kernel->yield('next_task', $hit);
2108 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2109 my $res = $incoming_db->exec_statement($sql);
2110 }
2112 $kernel->delay_set('watch_for_next_tasks', 1);
2113 }
2116 sub get_ldap_handle {
2117 my ($session_id) = @_;
2118 my $heap;
2119 my $ldap_handle;
2121 if (not defined $session_id ) { $session_id = 0 };
2122 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2124 if ($session_id == 0) {
2125 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2126 $ldap_handle = Net::LDAP->new( $ldap_uri );
2127 if (defined $ldap_handle) {
2128 $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!");
2129 } else {
2130 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2131 }
2133 } else {
2134 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2135 if( defined $session_reference ) {
2136 $heap = $session_reference->get_heap();
2137 }
2139 if (not defined $heap) {
2140 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2141 return;
2142 }
2144 # TODO: This "if" is nonsense, because it doesn't prove that the
2145 # used handle is still valid - or if we've to reconnect...
2146 #if (not exists $heap->{ldap_handle}) {
2147 $ldap_handle = Net::LDAP->new( $ldap_uri );
2148 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2149 $heap->{ldap_handle} = $ldap_handle;
2150 #}
2151 }
2152 return $ldap_handle;
2153 }
2156 sub change_fai_state {
2157 my ($st, $targets, $session_id) = @_;
2158 $session_id = 0 if not defined $session_id;
2159 # Set FAI state to localboot
2160 my %mapActions= (
2161 reboot => '',
2162 update => 'softupdate',
2163 localboot => 'localboot',
2164 reinstall => 'install',
2165 rescan => '',
2166 wake => '',
2167 memcheck => 'memcheck',
2168 sysinfo => 'sysinfo',
2169 install => 'install',
2170 );
2172 # Return if this is unknown
2173 if (!exists $mapActions{ $st }){
2174 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2175 return;
2176 }
2178 my $state= $mapActions{ $st };
2180 my $ldap_handle = &get_ldap_handle($session_id);
2181 if( defined($ldap_handle) ) {
2183 # Build search filter for hosts
2184 my $search= "(&(objectClass=GOhard)";
2185 foreach (@{$targets}){
2186 $search.= "(macAddress=$_)";
2187 }
2188 $search.= ")";
2190 # If there's any host inside of the search string, procress them
2191 if (!($search =~ /macAddress/)){
2192 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2193 return;
2194 }
2196 # Perform search for Unit Tag
2197 my $mesg = $ldap_handle->search(
2198 base => $ldap_base,
2199 scope => 'sub',
2200 attrs => ['dn', 'FAIstate', 'objectClass'],
2201 filter => "$search"
2202 );
2204 if ($mesg->count) {
2205 my @entries = $mesg->entries;
2206 if (0 == @entries) {
2207 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2208 }
2210 foreach my $entry (@entries) {
2211 # Only modify entry if it is not set to '$state'
2212 if ($entry->get_value("FAIstate") ne "$state"){
2213 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2214 my $result;
2215 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2216 if (exists $tmp{'FAIobject'}){
2217 if ($state eq ''){
2218 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2219 } else {
2220 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2221 }
2222 } elsif ($state ne ''){
2223 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2224 }
2226 # Errors?
2227 if ($result->code){
2228 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2229 }
2230 } else {
2231 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2232 }
2233 }
2234 } else {
2235 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2236 }
2238 # if no ldap handle defined
2239 } else {
2240 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2241 }
2243 return;
2244 }
2247 sub change_goto_state {
2248 my ($st, $targets, $session_id) = @_;
2249 $session_id = 0 if not defined $session_id;
2251 # Switch on or off?
2252 my $state= $st eq 'active' ? 'active': 'locked';
2254 my $ldap_handle = &get_ldap_handle($session_id);
2255 if( defined($ldap_handle) ) {
2257 # Build search filter for hosts
2258 my $search= "(&(objectClass=GOhard)";
2259 foreach (@{$targets}){
2260 $search.= "(macAddress=$_)";
2261 }
2262 $search.= ")";
2264 # If there's any host inside of the search string, procress them
2265 if (!($search =~ /macAddress/)){
2266 return;
2267 }
2269 # Perform search for Unit Tag
2270 my $mesg = $ldap_handle->search(
2271 base => $ldap_base,
2272 scope => 'sub',
2273 attrs => ['dn', 'gotoMode'],
2274 filter => "$search"
2275 );
2277 if ($mesg->count) {
2278 my @entries = $mesg->entries;
2279 foreach my $entry (@entries) {
2281 # Only modify entry if it is not set to '$state'
2282 if ($entry->get_value("gotoMode") ne $state){
2284 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2285 my $result;
2286 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2288 # Errors?
2289 if ($result->code){
2290 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2291 }
2293 }
2294 }
2295 } else {
2296 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2297 }
2299 }
2300 }
2303 sub run_recreate_packages_db {
2304 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2305 my $session_id = $session->ID;
2306 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2307 $kernel->yield('create_fai_release_db', $fai_release_tn);
2308 $kernel->yield('create_fai_server_db', $fai_server_tn);
2309 return;
2310 }
2313 sub run_create_fai_server_db {
2314 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2315 my $session_id = $session->ID;
2316 my $task = POE::Wheel::Run->new(
2317 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2318 StdoutEvent => "session_run_result",
2319 StderrEvent => "session_run_debug",
2320 CloseEvent => "session_run_done",
2321 );
2323 $heap->{task}->{ $task->ID } = $task;
2324 return;
2325 }
2328 sub create_fai_server_db {
2329 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2330 my $result;
2332 if (not defined $session_id) { $session_id = 0; }
2333 my $ldap_handle = &get_ldap_handle();
2334 if(defined($ldap_handle)) {
2335 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2336 my $mesg= $ldap_handle->search(
2337 base => $ldap_base,
2338 scope => 'sub',
2339 attrs => ['FAIrepository', 'gosaUnitTag'],
2340 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2341 );
2342 if($mesg->{'resultCode'} == 0 &&
2343 $mesg->count != 0) {
2344 foreach my $entry (@{$mesg->{entries}}) {
2345 if($entry->exists('FAIrepository')) {
2346 # Add an entry for each Repository configured for server
2347 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2348 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2349 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2350 $result= $fai_server_db->add_dbentry( {
2351 table => $table_name,
2352 primkey => ['server', 'fai_release', 'tag'],
2353 server => $tmp_url,
2354 fai_release => $tmp_release,
2355 sections => $tmp_sections,
2356 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2357 } );
2358 }
2359 }
2360 }
2361 }
2362 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2364 # TODO: Find a way to post the 'create_packages_list_db' event
2365 if(not defined($dont_create_packages_list)) {
2366 &create_packages_list_db(undef, undef, $session_id);
2367 }
2368 }
2370 $ldap_handle->disconnect;
2371 return $result;
2372 }
2375 sub run_create_fai_release_db {
2376 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2377 my $session_id = $session->ID;
2378 my $task = POE::Wheel::Run->new(
2379 Program => sub { &create_fai_release_db($table_name, $session_id) },
2380 StdoutEvent => "session_run_result",
2381 StderrEvent => "session_run_debug",
2382 CloseEvent => "session_run_done",
2383 );
2385 $heap->{task}->{ $task->ID } = $task;
2386 return;
2387 }
2390 sub create_fai_release_db {
2391 my ($table_name, $session_id) = @_;
2392 my $result;
2394 # used for logging
2395 if (not defined $session_id) { $session_id = 0; }
2397 my $ldap_handle = &get_ldap_handle();
2398 if(defined($ldap_handle)) {
2399 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2400 my $mesg= $ldap_handle->search(
2401 base => $ldap_base,
2402 scope => 'sub',
2403 attrs => [],
2404 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2405 );
2406 if($mesg->{'resultCode'} == 0 &&
2407 $mesg->count != 0) {
2408 # Walk through all possible FAI container ou's
2409 my @sql_list;
2410 my $timestamp= &get_time();
2411 foreach my $ou (@{$mesg->{entries}}) {
2412 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2413 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2414 my @tmp_array=get_fai_release_entries($tmp_classes);
2415 if(@tmp_array) {
2416 foreach my $entry (@tmp_array) {
2417 if(defined($entry) && ref($entry) eq 'HASH') {
2418 my $sql=
2419 "INSERT INTO $table_name "
2420 ."(timestamp, fai_release, class, type, state) VALUES ("
2421 .$timestamp.","
2422 ."'".$entry->{'release'}."',"
2423 ."'".$entry->{'class'}."',"
2424 ."'".$entry->{'type'}."',"
2425 ."'".$entry->{'state'}."')";
2426 push @sql_list, $sql;
2427 }
2428 }
2429 }
2430 }
2431 }
2433 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2434 if(@sql_list) {
2435 unshift @sql_list, "DELETE FROM $table_name";
2436 $fai_release_db->exec_statementlist(\@sql_list);
2437 }
2438 daemon_log("$session_id DEBUG: Done with inserting",7);
2439 }
2440 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2441 }
2442 $ldap_handle->disconnect;
2443 return $result;
2444 }
2446 sub get_fai_types {
2447 my $tmp_classes = shift || return undef;
2448 my @result;
2450 foreach my $type(keys %{$tmp_classes}) {
2451 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2452 my $entry = {
2453 type => $type,
2454 state => $tmp_classes->{$type}[0],
2455 };
2456 push @result, $entry;
2457 }
2458 }
2460 return @result;
2461 }
2463 sub get_fai_state {
2464 my $result = "";
2465 my $tmp_classes = shift || return $result;
2467 foreach my $type(keys %{$tmp_classes}) {
2468 if(defined($tmp_classes->{$type}[0])) {
2469 $result = $tmp_classes->{$type}[0];
2471 # State is equal for all types in class
2472 last;
2473 }
2474 }
2476 return $result;
2477 }
2479 sub resolve_fai_classes {
2480 my ($fai_base, $ldap_handle, $session_id) = @_;
2481 if (not defined $session_id) { $session_id = 0; }
2482 my $result;
2483 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2484 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2485 my $fai_classes;
2487 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2488 my $mesg= $ldap_handle->search(
2489 base => $fai_base,
2490 scope => 'sub',
2491 attrs => ['cn','objectClass','FAIstate'],
2492 filter => $fai_filter,
2493 );
2494 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2496 if($mesg->{'resultCode'} == 0 &&
2497 $mesg->count != 0) {
2498 foreach my $entry (@{$mesg->{entries}}) {
2499 if($entry->exists('cn')) {
2500 my $tmp_dn= $entry->dn();
2502 # Skip classname and ou dn parts for class
2503 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2505 # Skip classes without releases
2506 if((!defined($tmp_release)) || length($tmp_release)==0) {
2507 next;
2508 }
2510 my $tmp_cn= $entry->get_value('cn');
2511 my $tmp_state= $entry->get_value('FAIstate');
2513 my $tmp_type;
2514 # Get FAI type
2515 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2516 if(grep $_ eq $oclass, @possible_fai_classes) {
2517 $tmp_type= $oclass;
2518 last;
2519 }
2520 }
2522 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2523 # A Subrelease
2524 my @sub_releases = split(/,/, $tmp_release);
2526 # Walk through subreleases and build hash tree
2527 my $hash;
2528 while(my $tmp_sub_release = pop @sub_releases) {
2529 $hash .= "\{'$tmp_sub_release'\}->";
2530 }
2531 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2532 } else {
2533 # A branch, no subrelease
2534 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2535 }
2536 } elsif (!$entry->exists('cn')) {
2537 my $tmp_dn= $entry->dn();
2538 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2540 # Skip classes without releases
2541 if((!defined($tmp_release)) || length($tmp_release)==0) {
2542 next;
2543 }
2545 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2546 # A Subrelease
2547 my @sub_releases= split(/,/, $tmp_release);
2549 # Walk through subreleases and build hash tree
2550 my $hash;
2551 while(my $tmp_sub_release = pop @sub_releases) {
2552 $hash .= "\{'$tmp_sub_release'\}->";
2553 }
2554 # Remove the last two characters
2555 chop($hash);
2556 chop($hash);
2558 eval('$fai_classes->'.$hash.'= {}');
2559 } else {
2560 # A branch, no subrelease
2561 if(!exists($fai_classes->{$tmp_release})) {
2562 $fai_classes->{$tmp_release} = {};
2563 }
2564 }
2565 }
2566 }
2568 # The hash is complete, now we can honor the copy-on-write based missing entries
2569 foreach my $release (keys %$fai_classes) {
2570 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2571 }
2572 }
2573 return $result;
2574 }
2576 sub apply_fai_inheritance {
2577 my $fai_classes = shift || return {};
2578 my $tmp_classes;
2580 # Get the classes from the branch
2581 foreach my $class (keys %{$fai_classes}) {
2582 # Skip subreleases
2583 if($class =~ /^ou=.*$/) {
2584 next;
2585 } else {
2586 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2587 }
2588 }
2590 # Apply to each subrelease
2591 foreach my $subrelease (keys %{$fai_classes}) {
2592 if($subrelease =~ /ou=/) {
2593 foreach my $tmp_class (keys %{$tmp_classes}) {
2594 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2595 $fai_classes->{$subrelease}->{$tmp_class} =
2596 deep_copy($tmp_classes->{$tmp_class});
2597 } else {
2598 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2599 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2600 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2601 deep_copy($tmp_classes->{$tmp_class}->{$type});
2602 }
2603 }
2604 }
2605 }
2606 }
2607 }
2609 # Find subreleases in deeper levels
2610 foreach my $subrelease (keys %{$fai_classes}) {
2611 if($subrelease =~ /ou=/) {
2612 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2613 if($subsubrelease =~ /ou=/) {
2614 apply_fai_inheritance($fai_classes->{$subrelease});
2615 }
2616 }
2617 }
2618 }
2620 return $fai_classes;
2621 }
2623 sub get_fai_release_entries {
2624 my $tmp_classes = shift || return;
2625 my $parent = shift || "";
2626 my @result = shift || ();
2628 foreach my $entry (keys %{$tmp_classes}) {
2629 if(defined($entry)) {
2630 if($entry =~ /^ou=.*$/) {
2631 my $release_name = $entry;
2632 $release_name =~ s/ou=//g;
2633 if(length($parent)>0) {
2634 $release_name = $parent."/".$release_name;
2635 }
2636 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2637 foreach my $bufentry(@bufentries) {
2638 push @result, $bufentry;
2639 }
2640 } else {
2641 my @types = get_fai_types($tmp_classes->{$entry});
2642 foreach my $type (@types) {
2643 push @result,
2644 {
2645 'class' => $entry,
2646 'type' => $type->{'type'},
2647 'release' => $parent,
2648 'state' => $type->{'state'},
2649 };
2650 }
2651 }
2652 }
2653 }
2655 return @result;
2656 }
2658 sub deep_copy {
2659 my $this = shift;
2660 if (not ref $this) {
2661 $this;
2662 } elsif (ref $this eq "ARRAY") {
2663 [map deep_copy($_), @$this];
2664 } elsif (ref $this eq "HASH") {
2665 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2666 } else { die "what type is $_?" }
2667 }
2670 sub session_run_result {
2671 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2672 $kernel->sig(CHLD => "child_reap");
2673 }
2675 sub session_run_debug {
2676 my $result = $_[ARG0];
2677 print STDERR "$result\n";
2678 }
2680 sub session_run_done {
2681 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2682 delete $heap->{task}->{$task_id};
2683 }
2686 sub create_sources_list {
2687 my $session_id = shift;
2688 my $ldap_handle = &main::get_ldap_handle;
2689 my $result="/tmp/gosa_si_tmp_sources_list";
2691 # Remove old file
2692 if(stat($result)) {
2693 unlink($result);
2694 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2695 }
2697 my $fh;
2698 open($fh, ">$result");
2699 if (not defined $fh) {
2700 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2701 return undef;
2702 }
2703 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2704 my $mesg=$ldap_handle->search(
2705 base => $main::ldap_server_dn,
2706 scope => 'base',
2707 attrs => 'FAIrepository',
2708 filter => 'objectClass=FAIrepositoryServer'
2709 );
2710 if($mesg->count) {
2711 foreach my $entry(@{$mesg->{'entries'}}) {
2712 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2713 my ($server, $tag, $release, $sections)= split /\|/, $value;
2714 my $line = "deb $server $release";
2715 $sections =~ s/,/ /g;
2716 $line.= " $sections";
2717 print $fh $line."\n";
2718 }
2719 }
2720 }
2721 } else {
2722 if (defined $main::ldap_server_dn){
2723 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2724 } else {
2725 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2726 }
2727 }
2728 close($fh);
2730 return $result;
2731 }
2734 sub run_create_packages_list_db {
2735 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2736 my $session_id = $session->ID;
2738 my $task = POE::Wheel::Run->new(
2739 Priority => +20,
2740 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2741 StdoutEvent => "session_run_result",
2742 StderrEvent => "session_run_debug",
2743 CloseEvent => "session_run_done",
2744 );
2745 $heap->{task}->{ $task->ID } = $task;
2746 }
2749 sub create_packages_list_db {
2750 my ($ldap_handle, $sources_file, $session_id) = @_;
2752 # it should not be possible to trigger a recreation of packages_list_db
2753 # while packages_list_db is under construction, so set flag packages_list_under_construction
2754 # which is tested befor recreation can be started
2755 if (-r $packages_list_under_construction) {
2756 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2757 return;
2758 } else {
2759 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2760 # set packages_list_under_construction to true
2761 system("touch $packages_list_under_construction");
2762 @packages_list_statements=();
2763 }
2765 if (not defined $session_id) { $session_id = 0; }
2766 if (not defined $ldap_handle) {
2767 $ldap_handle= &get_ldap_handle();
2769 if (not defined $ldap_handle) {
2770 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2771 unlink($packages_list_under_construction);
2772 return;
2773 }
2774 }
2775 if (not defined $sources_file) {
2776 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2777 $sources_file = &create_sources_list($session_id);
2778 }
2780 if (not defined $sources_file) {
2781 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2782 unlink($packages_list_under_construction);
2783 return;
2784 }
2786 my $line;
2788 open(CONFIG, "<$sources_file") or do {
2789 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2790 unlink($packages_list_under_construction);
2791 return;
2792 };
2794 # Read lines
2795 while ($line = <CONFIG>){
2796 # Unify
2797 chop($line);
2798 $line =~ s/^\s+//;
2799 $line =~ s/^\s+/ /;
2801 # Strip comments
2802 $line =~ s/#.*$//g;
2804 # Skip empty lines
2805 if ($line =~ /^\s*$/){
2806 next;
2807 }
2809 # Interpret deb line
2810 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2811 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2812 my $section;
2813 foreach $section (split(' ', $sections)){
2814 &parse_package_info( $baseurl, $dist, $section, $session_id );
2815 }
2816 }
2817 }
2819 close (CONFIG);
2821 if(keys(%repo_dirs)) {
2822 find(\&cleanup_and_extract, keys( %repo_dirs ));
2823 &main::strip_packages_list_statements();
2824 $packages_list_db->exec_statementlist(\@packages_list_statements);
2825 }
2826 unlink($packages_list_under_construction);
2827 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2828 return;
2829 }
2831 # This function should do some intensive task to minimize the db-traffic
2832 sub strip_packages_list_statements {
2833 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2834 my @new_statement_list=();
2835 my $hash;
2836 my $insert_hash;
2837 my $update_hash;
2838 my $delete_hash;
2839 my $known_packages_hash;
2840 my $local_timestamp=get_time();
2842 foreach my $existing_entry (@existing_entries) {
2843 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2844 }
2846 foreach my $statement (@packages_list_statements) {
2847 if($statement =~ /^INSERT/i) {
2848 # Assign the values from the insert statement
2849 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2850 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2851 if(exists($hash->{$distribution}->{$package}->{$version})) {
2852 # If section or description has changed, update the DB
2853 if(
2854 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2855 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2856 ) {
2857 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2858 } else {
2859 # package is already present in database. cache this knowledge for later use
2860 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2861 }
2862 } else {
2863 # Insert a non-existing entry to db
2864 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2865 }
2866 } elsif ($statement =~ /^UPDATE/i) {
2867 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2868 /^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;
2869 foreach my $distribution (keys %{$hash}) {
2870 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2871 # update the insertion hash to execute only one query per package (insert instead insert+update)
2872 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2873 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2874 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2875 my $section;
2876 my $description;
2877 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2878 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2879 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2880 }
2881 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2882 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2883 }
2884 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2885 }
2886 }
2887 }
2888 }
2889 }
2891 # Check for orphaned entries
2892 foreach my $existing_entry (@existing_entries) {
2893 my $distribution= @{$existing_entry}[0];
2894 my $package= @{$existing_entry}[1];
2895 my $version= @{$existing_entry}[2];
2896 my $section= @{$existing_entry}[3];
2898 if(
2899 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2900 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2901 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2902 ) {
2903 next;
2904 } else {
2905 # Insert entry to delete hash
2906 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2907 }
2908 }
2910 # unroll the insert hash
2911 foreach my $distribution (keys %{$insert_hash}) {
2912 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2913 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2914 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2915 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2916 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2917 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2918 ."'$local_timestamp')";
2919 }
2920 }
2921 }
2923 # unroll the update hash
2924 foreach my $distribution (keys %{$update_hash}) {
2925 foreach my $package (keys %{$update_hash->{$distribution}}) {
2926 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2927 my $set = "";
2928 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2929 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2930 }
2931 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2932 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2933 }
2934 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2935 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2936 }
2937 if(defined($set) and length($set) > 0) {
2938 $set .= "timestamp = '$local_timestamp'";
2939 } else {
2940 next;
2941 }
2942 push @new_statement_list,
2943 "UPDATE $main::packages_list_tn SET $set WHERE"
2944 ." distribution = '$distribution'"
2945 ." AND package = '$package'"
2946 ." AND version = '$version'";
2947 }
2948 }
2949 }
2951 # unroll the delete hash
2952 foreach my $distribution (keys %{$delete_hash}) {
2953 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2954 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2955 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2956 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2957 }
2958 }
2959 }
2961 @packages_list_statements = @new_statement_list;
2962 }
2965 sub parse_package_info {
2966 my ($baseurl, $dist, $section, $session_id)= @_;
2967 my ($package);
2968 if (not defined $session_id) { $session_id = 0; }
2969 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2970 $repo_dirs{ "${repo_path}/pool" } = 1;
2972 foreach $package ("Packages.gz"){
2973 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2974 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2975 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2976 }
2978 }
2981 sub get_package {
2982 my ($url, $dest, $session_id)= @_;
2983 if (not defined $session_id) { $session_id = 0; }
2985 my $tpath = dirname($dest);
2986 -d "$tpath" || mkpath "$tpath";
2988 # This is ugly, but I've no time to take a look at "how it works in perl"
2989 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2990 system("gunzip -cd '$dest' > '$dest.in'");
2991 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2992 unlink($dest);
2993 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2994 } else {
2995 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2996 }
2997 return 0;
2998 }
3001 sub parse_package {
3002 my ($path, $dist, $srv_path, $session_id)= @_;
3003 if (not defined $session_id) { $session_id = 0;}
3004 my ($package, $version, $section, $description);
3005 my $PACKAGES;
3006 my $timestamp = &get_time();
3008 if(not stat("$path.in")) {
3009 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3010 return;
3011 }
3013 open($PACKAGES, "<$path.in");
3014 if(not defined($PACKAGES)) {
3015 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3016 return;
3017 }
3019 # Read lines
3020 while (<$PACKAGES>){
3021 my $line = $_;
3022 # Unify
3023 chop($line);
3025 # Use empty lines as a trigger
3026 if ($line =~ /^\s*$/){
3027 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3028 push(@packages_list_statements, $sql);
3029 $package = "none";
3030 $version = "none";
3031 $section = "none";
3032 $description = "none";
3033 next;
3034 }
3036 # Trigger for package name
3037 if ($line =~ /^Package:\s/){
3038 ($package)= ($line =~ /^Package: (.*)$/);
3039 next;
3040 }
3042 # Trigger for version
3043 if ($line =~ /^Version:\s/){
3044 ($version)= ($line =~ /^Version: (.*)$/);
3045 next;
3046 }
3048 # Trigger for description
3049 if ($line =~ /^Description:\s/){
3050 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3051 next;
3052 }
3054 # Trigger for section
3055 if ($line =~ /^Section:\s/){
3056 ($section)= ($line =~ /^Section: (.*)$/);
3057 next;
3058 }
3060 # Trigger for filename
3061 if ($line =~ /^Filename:\s/){
3062 my ($filename) = ($line =~ /^Filename: (.*)$/);
3063 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3064 next;
3065 }
3066 }
3068 close( $PACKAGES );
3069 unlink( "$path.in" );
3070 }
3073 sub store_fileinfo {
3074 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3076 my %fileinfo = (
3077 'package' => $package,
3078 'dist' => $dist,
3079 'version' => $vers,
3080 );
3082 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3083 }
3086 sub cleanup_and_extract {
3087 my $fileinfo = $repo_files{ $File::Find::name };
3089 if( defined $fileinfo ) {
3090 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3091 my $sql;
3092 my $package = $fileinfo->{ 'package' };
3093 my $newver = $fileinfo->{ 'version' };
3095 mkpath($dir);
3096 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3098 if( -f "$dir/DEBIAN/templates" ) {
3100 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3102 my $tmpl= ""; {
3103 local $/=undef;
3104 open FILE, "$dir/DEBIAN/templates";
3105 $tmpl = &encode_base64(<FILE>);
3106 close FILE;
3107 }
3108 rmtree("$dir/DEBIAN/templates");
3110 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3111 push @packages_list_statements, $sql;
3112 }
3113 }
3115 return;
3116 }
3119 sub register_at_foreign_servers {
3120 my ($kernel) = $_[KERNEL];
3122 # hole alle bekannten server aus known_server_db
3123 my $server_sql = "SELECT * FROM $known_server_tn";
3124 my $server_res = $known_server_db->exec_statement($server_sql);
3126 # no entries in known_server_db
3127 if (not ref(@$server_res[0]) eq "ARRAY") {
3128 # TODO
3129 }
3131 # detect already connected clients
3132 my $client_sql = "SELECT * FROM $known_clients_tn";
3133 my $client_res = $known_clients_db->exec_statement($client_sql);
3135 # send my server details to all other gosa-si-server within the network
3136 foreach my $hit (@$server_res) {
3137 my $hostname = @$hit[0];
3138 my $hostkey = &create_passwd;
3140 # add already connected clients to registration message
3141 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3142 &add_content2xml_hash($myhash, 'key', $hostkey);
3143 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3145 # add locally loaded gosa-si modules to registration message
3146 my $loaded_modules = {};
3147 while (my ($package, $pck_info) = each %$known_modules) {
3148 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3149 foreach my $act_module (keys(%{@$pck_info[2]})) {
3150 $loaded_modules->{$act_module} = "";
3151 }
3152 }
3154 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3156 # add macaddress to registration message
3157 my ($host_ip, $host_port) = split(/:/, $hostname);
3158 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3159 my $network_interface= &get_interface_for_ip($local_ip);
3160 my $host_mac = &get_mac_for_interface($network_interface);
3161 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3163 # build registration message and send it
3164 my $foreign_server_msg = &create_xml_string($myhash);
3165 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3166 }
3168 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3169 return;
3170 }
3173 #==== MAIN = main ==============================================================
3174 # parse commandline options
3175 Getopt::Long::Configure( "bundling" );
3176 GetOptions("h|help" => \&usage,
3177 "c|config=s" => \$cfg_file,
3178 "f|foreground" => \$foreground,
3179 "v|verbose+" => \$verbose,
3180 "no-arp+" => \$no_arp,
3181 );
3183 # read and set config parameters
3184 &check_cmdline_param ;
3185 &read_configfile($cfg_file, %cfg_defaults);
3186 &check_pid;
3188 $SIG{CHLD} = 'IGNORE';
3190 # forward error messages to logfile
3191 if( ! $foreground ) {
3192 open( STDIN, '+>/dev/null' );
3193 open( STDOUT, '+>&STDIN' );
3194 open( STDERR, '+>&STDIN' );
3195 }
3197 # Just fork, if we are not in foreground mode
3198 if( ! $foreground ) {
3199 chdir '/' or die "Can't chdir to /: $!";
3200 $pid = fork;
3201 setsid or die "Can't start a new session: $!";
3202 umask 0;
3203 } else {
3204 $pid = $$;
3205 }
3207 # Do something useful - put our PID into the pid_file
3208 if( 0 != $pid ) {
3209 open( LOCK_FILE, ">$pid_file" );
3210 print LOCK_FILE "$pid\n";
3211 close( LOCK_FILE );
3212 if( !$foreground ) {
3213 exit( 0 )
3214 };
3215 }
3217 # parse head url and revision from svn
3218 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3219 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3220 $server_headURL = defined $1 ? $1 : 'unknown' ;
3221 $server_revision = defined $2 ? $2 : 'unknown' ;
3222 if ($server_headURL =~ /\/tag\// ||
3223 $server_headURL =~ /\/branches\// ) {
3224 $server_status = "stable";
3225 } else {
3226 $server_status = "developmental" ;
3227 }
3229 # Prepare log file
3230 $root_uid = getpwnam('root');
3231 $adm_gid = getgrnam('adm');
3232 chmod(0640, $log_file);
3233 chown($root_uid, $adm_gid, $log_file);
3234 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3236 daemon_log(" ", 1);
3237 daemon_log("$0 started!", 1);
3238 daemon_log("status: $server_status", 1);
3239 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3241 {
3242 no strict "refs";
3244 if ($db_module eq "DBmysql") {
3245 # connect to incoming_db
3246 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3248 # connect to gosa-si job queue
3249 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3251 # connect to known_clients_db
3252 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3254 # connect to foreign_clients_db
3255 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3257 # connect to known_server_db
3258 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3260 # connect to login_usr_db
3261 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3263 # connect to fai_server_db
3264 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3266 # connect to fai_release_db
3267 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3269 # connect to packages_list_db
3270 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3272 # connect to messaging_db
3273 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3275 } elsif ($db_module eq "DBsqlite") {
3276 # connect to incoming_db
3277 unlink($incoming_file_name);
3278 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3280 # connect to gosa-si job queue
3281 unlink($job_queue_file_name); ## just for debugging
3282 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3283 chmod(0660, $job_queue_file_name);
3284 chown($root_uid, $adm_gid, $job_queue_file_name);
3286 # connect to known_clients_db
3287 unlink($known_clients_file_name); ## just for debugging
3288 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3289 chmod(0660, $known_clients_file_name);
3290 chown($root_uid, $adm_gid, $known_clients_file_name);
3292 # connect to foreign_clients_db
3293 unlink($foreign_clients_file_name);
3294 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3295 chmod(0660, $foreign_clients_file_name);
3296 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3298 # connect to known_server_db
3299 unlink($known_server_file_name);
3300 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3301 chmod(0660, $known_server_file_name);
3302 chown($root_uid, $adm_gid, $known_server_file_name);
3304 # connect to login_usr_db
3305 unlink($login_users_file_name);
3306 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3307 chmod(0660, $login_users_file_name);
3308 chown($root_uid, $adm_gid, $login_users_file_name);
3310 # connect to fai_server_db
3311 unlink($fai_server_file_name);
3312 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3313 chmod(0660, $fai_server_file_name);
3314 chown($root_uid, $adm_gid, $fai_server_file_name);
3316 # connect to fai_release_db
3317 unlink($fai_release_file_name);
3318 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3319 chmod(0660, $fai_release_file_name);
3320 chown($root_uid, $adm_gid, $fai_release_file_name);
3322 # connect to packages_list_db
3323 #unlink($packages_list_file_name);
3324 unlink($packages_list_under_construction);
3325 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3326 chmod(0660, $packages_list_file_name);
3327 chown($root_uid, $adm_gid, $packages_list_file_name);
3329 # connect to messaging_db
3330 unlink($messaging_file_name);
3331 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3332 chmod(0660, $messaging_file_name);
3333 chown($root_uid, $adm_gid, $messaging_file_name);
3334 }
3335 }
3337 # Creating tables
3338 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3339 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3340 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3341 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3342 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3343 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3344 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3345 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3346 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3347 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3350 # create xml object used for en/decrypting
3351 $xml = new XML::Simple();
3354 # foreign servers
3355 my @foreign_server_list;
3357 # add foreign server from cfg file
3358 if ($foreign_server_string ne "") {
3359 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3360 foreach my $foreign_server (@cfg_foreign_server_list) {
3361 push(@foreign_server_list, $foreign_server);
3362 }
3364 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3365 }
3367 # Perform a DNS lookup for server registration if flag is true
3368 if ($dns_lookup eq "true") {
3369 # Add foreign server from dns
3370 my @tmp_servers;
3371 if (not $server_domain) {
3372 # Try our DNS Searchlist
3373 for my $domain(get_dns_domains()) {
3374 chomp($domain);
3375 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3376 if(@$tmp_domains) {
3377 for my $tmp_server(@$tmp_domains) {
3378 push @tmp_servers, $tmp_server;
3379 }
3380 }
3381 }
3382 if(@tmp_servers && length(@tmp_servers)==0) {
3383 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3384 }
3385 } else {
3386 @tmp_servers = &get_server_addresses($server_domain);
3387 if( 0 == @tmp_servers ) {
3388 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3389 }
3390 }
3392 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3394 foreach my $server (@tmp_servers) {
3395 unshift(@foreign_server_list, $server);
3396 }
3397 } else {
3398 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3399 }
3402 # eliminate duplicate entries
3403 @foreign_server_list = &del_doubles(@foreign_server_list);
3404 my $all_foreign_server = join(", ", @foreign_server_list);
3405 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3407 # add all found foreign servers to known_server
3408 my $act_timestamp = &get_time();
3409 foreach my $foreign_server (@foreign_server_list) {
3411 # do not add myself to known_server_db
3412 if (&is_local($foreign_server)) { next; }
3413 ######################################
3415 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3416 primkey=>['hostname'],
3417 hostname=>$foreign_server,
3418 macaddress=>"",
3419 status=>'not_jet_registered',
3420 hostkey=>"none",
3421 loaded_modules => "none",
3422 timestamp=>$act_timestamp,
3423 } );
3424 }
3427 # Import all modules
3428 &import_modules;
3430 # Check wether all modules are gosa-si valid passwd check
3431 &password_check;
3433 # Prepare for using Opsi
3434 if ($opsi_enabled eq "true") {
3435 use JSON::RPC::Client;
3436 use XML::Quote qw(:all);
3437 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3438 $opsi_client = new JSON::RPC::Client;
3439 }
3442 POE::Component::Server::TCP->new(
3443 Alias => "TCP_SERVER",
3444 Port => $server_port,
3445 ClientInput => sub {
3446 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3447 my $session_id = $session->ID;
3448 my $remote_ip = $heap->{'remote_ip'};
3449 push(@msgs_to_decrypt, $input);
3450 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3451 $kernel->yield("msg_to_decrypt");
3452 },
3453 InlineStates => {
3454 msg_to_decrypt => \&msg_to_decrypt,
3455 next_task => \&next_task,
3456 task_result => \&handle_task_result,
3457 task_done => \&handle_task_done,
3458 task_debug => \&handle_task_debug,
3459 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3460 }
3461 );
3463 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3465 # create session for repeatedly checking the job queue for jobs
3466 POE::Session->create(
3467 inline_states => {
3468 _start => \&session_start,
3469 register_at_foreign_servers => \®ister_at_foreign_servers,
3470 sig_handler => \&sig_handler,
3471 next_task => \&next_task,
3472 task_result => \&handle_task_result,
3473 task_done => \&handle_task_done,
3474 task_debug => \&handle_task_debug,
3475 watch_for_next_tasks => \&watch_for_next_tasks,
3476 watch_for_new_messages => \&watch_for_new_messages,
3477 watch_for_delivery_messages => \&watch_for_delivery_messages,
3478 watch_for_done_messages => \&watch_for_done_messages,
3479 watch_for_new_jobs => \&watch_for_new_jobs,
3480 watch_for_modified_jobs => \&watch_for_modified_jobs,
3481 watch_for_done_jobs => \&watch_for_done_jobs,
3482 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3483 watch_for_old_known_clients => \&watch_for_old_known_clients,
3484 create_packages_list_db => \&run_create_packages_list_db,
3485 create_fai_server_db => \&run_create_fai_server_db,
3486 create_fai_release_db => \&run_create_fai_release_db,
3487 recreate_packages_db => \&run_recreate_packages_db,
3488 session_run_result => \&session_run_result,
3489 session_run_debug => \&session_run_debug,
3490 session_run_done => \&session_run_done,
3491 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3492 }
3493 );
3496 POE::Kernel->run();
3497 exit;