22986304d2a08b184e1eaaed98d22bbf65f44645
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 ) {
1013 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1014 my $sql_statement = "UPDATE $job_queue_tn ".
1015 "SET status='error', result='can not deliver msg, please consult log file' ".
1016 "WHERE id=$jobdb_id";
1017 my $res = $job_db->update_dbentry($sql_statement);
1018 }
1020 # sending msg was successful
1021 } else {
1022 my $sql_statement = "UPDATE $job_queue_tn ".
1023 "SET status='done' ".
1024 "WHERE id=$jobdb_id AND status='processed'";
1025 my $res = $job_db->update_dbentry($sql_statement);
1026 }
1027 }
1028 }
1031 sub sig_handler {
1032 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1033 daemon_log("0 INFO got signal '$signal'", 1);
1034 $kernel->sig_handled();
1035 return;
1036 }
1039 sub msg_to_decrypt {
1040 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1041 my $session_id = $session->ID;
1042 my ($msg, $msg_hash, $module);
1043 my $error = 0;
1045 # hole neue msg aus @msgs_to_decrypt
1046 my $next_msg = shift @msgs_to_decrypt;
1048 # msg is from a new client or gosa
1049 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1051 # msg is from a gosa-si-server
1052 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1054 }
1055 # msg is from a gosa-si-client
1056 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1058 }
1059 # an error occurred
1060 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1061 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1062 # could not understand a msg from its server the client cause a re-registering process
1063 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1064 "' to cause a re-registering of the client if necessary", 3);
1065 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1066 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1067 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1068 my $host_name = $hit->{'hostname'};
1069 my $host_key = $hit->{'hostkey'};
1070 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1071 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1072 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1073 }
1074 $error++;
1075 }
1078 my $header;
1079 my $target;
1080 my $source;
1081 my $done = 0;
1082 my $sql;
1083 my $res;
1085 # check whether this message should be processed here
1086 if ($error == 0) {
1087 $header = @{$msg_hash->{'header'}}[0];
1088 $target = @{$msg_hash->{'target'}}[0];
1089 $source = @{$msg_hash->{'source'}}[0];
1090 my $not_found_in_known_clients_db = 0;
1091 my $not_found_in_known_server_db = 0;
1092 my $not_found_in_foreign_clients_db = 0;
1093 my $local_address;
1094 my $local_mac;
1095 my ($target_ip, $target_port) = split(':', $target);
1097 # Determine the local ip address if target is an ip address
1098 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1099 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1100 } else {
1101 $local_address = $server_address;
1102 }
1104 # Determine the local mac address if target is a mac address
1105 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) {
1106 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1107 my $network_interface= &get_interface_for_ip($loc_ip);
1108 $local_mac = &get_mac_for_interface($network_interface);
1109 } else {
1110 $local_mac = $server_mac_address;
1111 }
1113 # target and source is equal to GOSA -> process here
1114 if (not $done) {
1115 if ($target eq "GOSA" && $source eq "GOSA") {
1116 $done = 1;
1117 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1118 }
1119 }
1121 # target is own address without forward_to_gosa-tag -> process here
1122 if (not $done) {
1123 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1124 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1125 $done = 1;
1126 if ($source eq "GOSA") {
1127 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1128 }
1129 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1130 }
1131 }
1133 # target is a client address in known_clients -> process here
1134 if (not $done) {
1135 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1136 $res = $known_clients_db->select_dbentry($sql);
1137 if (keys(%$res) > 0) {
1138 $done = 1;
1139 my $hostname = $res->{1}->{'hostname'};
1140 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1141 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1142 if ($source eq "GOSA") {
1143 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1144 }
1145 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1147 } else {
1148 $not_found_in_known_clients_db = 1;
1149 }
1150 }
1152 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1153 if (not $done) {
1154 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1155 my $gosa_at;
1156 my $gosa_session_id;
1157 if (($target eq $local_address) && (defined $forward_to_gosa)){
1158 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1159 if ($gosa_at ne $local_address) {
1160 $done = 1;
1161 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1162 }
1163 }
1164 }
1166 # if message should be processed here -> add message to incoming_db
1167 if ($done) {
1168 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1169 # so gosa-si-server knows how to process this kind of messages
1170 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1171 $module = "GosaPackages";
1172 }
1174 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1175 primkey=>[],
1176 headertag=>$header,
1177 targettag=>$target,
1178 xmlmessage=>&encode_base64($msg),
1179 timestamp=>&get_time,
1180 module=>$module,
1181 sessionid=>$session_id,
1182 } );
1184 }
1186 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1187 if (not $done) {
1188 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1189 my $gosa_at;
1190 my $gosa_session_id;
1191 if (($target eq $local_address) && (defined $forward_to_gosa)){
1192 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1193 if ($gosa_at eq $local_address) {
1194 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1195 if( defined $session_reference ) {
1196 $heap = $session_reference->get_heap();
1197 }
1198 if(exists $heap->{'client'}) {
1199 $msg = &encrypt_msg($msg, $GosaPackages_key);
1200 $heap->{'client'}->put($msg);
1201 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1202 }
1203 $done = 1;
1204 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1205 }
1206 }
1208 }
1210 # target is a client address in foreign_clients -> forward to registration server
1211 if (not $done) {
1212 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1213 $res = $foreign_clients_db->select_dbentry($sql);
1214 if (keys(%$res) > 0) {
1215 my $hostname = $res->{1}->{'hostname'};
1216 my ($host_ip, $host_port) = split(/:/, $hostname);
1217 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1218 my $regserver = $res->{1}->{'regserver'};
1219 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1220 my $res = $known_server_db->select_dbentry($sql);
1221 if (keys(%$res) > 0) {
1222 my $regserver_key = $res->{1}->{'hostkey'};
1223 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1224 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1225 if ($source eq "GOSA") {
1226 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1227 }
1228 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1229 }
1230 $done = 1;
1231 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1232 } else {
1233 $not_found_in_foreign_clients_db = 1;
1234 }
1235 }
1237 # target is a server address -> forward to server
1238 if (not $done) {
1239 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1240 $res = $known_server_db->select_dbentry($sql);
1241 if (keys(%$res) > 0) {
1242 my $hostkey = $res->{1}->{'hostkey'};
1244 if ($source eq "GOSA") {
1245 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1246 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1248 }
1250 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1251 $done = 1;
1252 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1253 } else {
1254 $not_found_in_known_server_db = 1;
1255 }
1256 }
1259 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1260 if ( $not_found_in_foreign_clients_db
1261 && $not_found_in_known_server_db
1262 && $not_found_in_known_clients_db) {
1263 &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);
1264 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1265 primkey=>[],
1266 headertag=>$header,
1267 targettag=>$target,
1268 xmlmessage=>&encode_base64($msg),
1269 timestamp=>&get_time,
1270 module=>$module,
1271 sessionid=>$session_id,
1272 } );
1273 $done = 1;
1274 }
1277 if (not $done) {
1278 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1279 if ($source eq "GOSA") {
1280 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1281 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1283 my $session_reference = $kernel->ID_id_to_session($session_id);
1284 if( defined $session_reference ) {
1285 $heap = $session_reference->get_heap();
1286 }
1287 if(exists $heap->{'client'}) {
1288 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1289 $heap->{'client'}->put($error_msg);
1290 }
1291 }
1292 }
1294 }
1296 return;
1297 }
1300 sub next_task {
1301 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1302 my $running_task = POE::Wheel::Run->new(
1303 Program => sub { process_task($session, $heap, $task) },
1304 StdioFilter => POE::Filter::Reference->new(),
1305 StdoutEvent => "task_result",
1306 StderrEvent => "task_debug",
1307 CloseEvent => "task_done",
1308 );
1309 $heap->{task}->{ $running_task->ID } = $running_task;
1310 }
1312 sub handle_task_result {
1313 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1314 my $client_answer = $result->{'answer'};
1315 if( $client_answer =~ s/session_id=(\d+)$// ) {
1316 my $session_id = $1;
1317 if( defined $session_id ) {
1318 my $session_reference = $kernel->ID_id_to_session($session_id);
1319 if( defined $session_reference ) {
1320 $heap = $session_reference->get_heap();
1321 }
1322 }
1324 if(exists $heap->{'client'}) {
1325 $heap->{'client'}->put($client_answer);
1326 }
1327 }
1328 $kernel->sig(CHLD => "child_reap");
1329 }
1331 sub handle_task_debug {
1332 my $result = $_[ARG0];
1333 print STDERR "$result\n";
1334 }
1336 sub handle_task_done {
1337 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1338 delete $heap->{task}->{$task_id};
1339 }
1341 sub process_task {
1342 no strict "refs";
1343 #CHECK: Not @_[...]?
1344 my ($session, $heap, $task) = @_;
1345 my $error = 0;
1346 my $answer_l;
1347 my ($answer_header, @answer_target_l, $answer_source);
1348 my $client_answer = "";
1350 # prepare all variables needed to process message
1351 #my $msg = $task->{'xmlmessage'};
1352 my $msg = &decode_base64($task->{'xmlmessage'});
1353 my $incoming_id = $task->{'id'};
1354 my $module = $task->{'module'};
1355 my $header = $task->{'headertag'};
1356 my $session_id = $task->{'sessionid'};
1357 my $msg_hash;
1358 eval {
1359 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1360 };
1361 daemon_log("ERROR: XML failure '$@'") if ($@);
1362 my $source = @{$msg_hash->{'source'}}[0];
1364 # set timestamp of incoming client uptodate, so client will not
1365 # be deleted from known_clients because of expiration
1366 my $act_time = &get_time();
1367 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1368 my $res = $known_clients_db->exec_statement($sql);
1370 ######################
1371 # process incoming msg
1372 if( $error == 0) {
1373 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1374 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1375 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1377 if ( 0 < @{$answer_l} ) {
1378 my $answer_str = join("\n", @{$answer_l});
1379 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1380 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1381 }
1382 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1383 } else {
1384 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1385 }
1387 }
1388 if( !$answer_l ) { $error++ };
1390 ########
1391 # answer
1392 if( $error == 0 ) {
1394 foreach my $answer ( @{$answer_l} ) {
1395 # check outgoing msg to xml validity
1396 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1397 if( not defined $answer_hash ) { next; }
1399 $answer_header = @{$answer_hash->{'header'}}[0];
1400 @answer_target_l = @{$answer_hash->{'target'}};
1401 $answer_source = @{$answer_hash->{'source'}}[0];
1403 # deliver msg to all targets
1404 foreach my $answer_target ( @answer_target_l ) {
1406 # targets of msg are all gosa-si-clients in known_clients_db
1407 if( $answer_target eq "*" ) {
1408 # answer is for all clients
1409 my $sql_statement= "SELECT * FROM known_clients";
1410 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1411 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1412 my $host_name = $hit->{hostname};
1413 my $host_key = $hit->{hostkey};
1414 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1415 &update_jobdb_status_for_send_msgs($answer, $error);
1416 }
1417 }
1419 # targets of msg are all gosa-si-server in known_server_db
1420 elsif( $answer_target eq "KNOWN_SERVER" ) {
1421 # answer is for all server in known_server
1422 my $sql_statement= "SELECT * FROM $known_server_tn";
1423 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1424 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1425 my $host_name = $hit->{hostname};
1426 my $host_key = $hit->{hostkey};
1427 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1428 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1429 &update_jobdb_status_for_send_msgs($answer, $error);
1430 }
1431 }
1433 # target of msg is GOsa
1434 elsif( $answer_target eq "GOSA" ) {
1435 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1436 my $add_on = "";
1437 if( defined $session_id ) {
1438 $add_on = ".session_id=$session_id";
1439 }
1440 # answer is for GOSA and has to returned to connected client
1441 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1442 $client_answer = $gosa_answer.$add_on;
1443 }
1445 # target of msg is job queue at this host
1446 elsif( $answer_target eq "JOBDB") {
1447 $answer =~ /<header>(\S+)<\/header>/;
1448 my $header;
1449 if( defined $1 ) { $header = $1; }
1450 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1451 &update_jobdb_status_for_send_msgs($answer, $error);
1452 }
1454 # Target of msg is a mac address
1455 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 ) {
1456 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1457 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1458 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1459 my $found_ip_flag = 0;
1460 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1461 my $host_name = $hit->{hostname};
1462 my $host_key = $hit->{hostkey};
1463 $answer =~ s/$answer_target/$host_name/g;
1464 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1465 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1466 &update_jobdb_status_for_send_msgs($answer, $error);
1467 $found_ip_flag++ ;
1468 }
1469 if ($found_ip_flag == 0) {
1470 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1471 my $res = $foreign_clients_db->select_dbentry($sql);
1472 while( my ($hit_num, $hit) = each %{ $res } ) {
1473 my $host_name = $hit->{hostname};
1474 my $reg_server = $hit->{regserver};
1475 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1477 # Fetch key for reg_server
1478 my $reg_server_key;
1479 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1480 my $res = $known_server_db->select_dbentry($sql);
1481 if (exists $res->{1}) {
1482 $reg_server_key = $res->{1}->{'hostkey'};
1483 } else {
1484 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1485 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1486 $reg_server_key = undef;
1487 }
1489 # Send answer to server where client is registered
1490 if (defined $reg_server_key) {
1491 $answer =~ s/$answer_target/$host_name/g;
1492 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1493 &update_jobdb_status_for_send_msgs($answer, $error);
1494 $found_ip_flag++ ;
1495 }
1496 }
1497 }
1498 if( $found_ip_flag == 0) {
1499 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1501 # 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
1502 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", 30);
1503 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$answer_target' AND headertag='$answer_header')";
1504 my $res = $job_db->update_dbentry($sql);
1505 daemon_log("$session_id INFO: '$answer_header'-job will be reactivated at '$delay_timestamp' ".
1506 "cause client '$answer_target' is currently not available", 5);
1507 daemon_log("$session_id $sql", 7);
1508 }
1510 # Answer is for one specific host
1511 } else {
1512 # get encrypt_key
1513 my $encrypt_key = &get_encrypt_key($answer_target);
1514 if( not defined $encrypt_key ) {
1515 # unknown target
1516 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1517 next;
1518 }
1519 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1520 &update_jobdb_status_for_send_msgs($answer, $error);
1521 }
1522 }
1523 }
1524 }
1526 my $filter = POE::Filter::Reference->new();
1527 my %result = (
1528 status => "seems ok to me",
1529 answer => $client_answer,
1530 );
1532 my $output = $filter->put( [ \%result ] );
1533 print @$output;
1536 }
1538 sub session_start {
1539 my ($kernel) = $_[KERNEL];
1540 $global_kernel = $kernel;
1541 $kernel->yield('register_at_foreign_servers');
1542 $kernel->yield('create_fai_server_db', $fai_server_tn );
1543 $kernel->yield('create_fai_release_db', $fai_release_tn );
1544 $kernel->yield('watch_for_next_tasks');
1545 $kernel->sig(USR1 => "sig_handler");
1546 $kernel->sig(USR2 => "recreate_packages_db");
1547 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1548 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1549 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1550 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1551 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1552 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1553 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1555 # Start opsi check
1556 if ($opsi_enabled eq "true") {
1557 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1558 }
1560 }
1563 sub watch_for_done_jobs {
1564 #CHECK: $heap for what?
1565 my ($kernel,$heap) = @_[KERNEL, HEAP];
1567 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1568 my $res = $job_db->select_dbentry( $sql_statement );
1570 while( my ($id, $hit) = each %{$res} ) {
1571 my $jobdb_id = $hit->{id};
1572 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1573 my $res = $job_db->del_dbentry($sql_statement);
1574 }
1576 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1577 }
1580 sub watch_for_opsi_jobs {
1581 my ($kernel) = $_[KERNEL];
1583 # 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
1584 # opsi install job is to parse the xml message. There is still the correct header.
1585 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1586 my $res = $job_db->select_dbentry( $sql_statement );
1588 # Ask OPSI for an update of the running jobs
1589 while (my ($id, $hit) = each %$res ) {
1590 # Determine current parameters of the job
1591 my $hostId = $hit->{'plainname'};
1592 my $macaddress = $hit->{'macaddress'};
1593 my $progress = $hit->{'progress'};
1595 my $result= {};
1597 # For hosts, only return the products that are or get installed
1598 my $callobj;
1599 $callobj = {
1600 method => 'getProductStates_hash',
1601 params => [ $hostId ],
1602 id => 1,
1603 };
1605 my $hres = $opsi_client->call($opsi_url, $callobj);
1606 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1607 if (not &check_opsi_res($hres)) {
1608 my $htmp= $hres->result->{$hostId};
1610 # Check state != not_installed or action == setup -> load and add
1611 my $products= 0;
1612 my $installed= 0;
1613 my $installing = 0;
1614 my $error= 0;
1615 my @installed_list;
1616 my @error_list;
1617 my $act_status = "none";
1618 foreach my $product (@{$htmp}){
1620 if ($product->{'installationStatus'} ne "not_installed" or
1621 $product->{'actionRequest'} eq "setup"){
1623 # Increase number of products for this host
1624 $products++;
1626 if ($product->{'installationStatus'} eq "failed"){
1627 $result->{$product->{'productId'}}= "error";
1628 unshift(@error_list, $product->{'productId'});
1629 $error++;
1630 }
1631 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1632 $result->{$product->{'productId'}}= "installed";
1633 unshift(@installed_list, $product->{'productId'});
1634 $installed++;
1635 }
1636 if ($product->{'installationStatus'} eq "installing"){
1637 $result->{$product->{'productId'}}= "installing";
1638 $installing++;
1639 $act_status = "installing - ".$product->{'productId'};
1640 }
1641 }
1642 }
1644 # Estimate "rough" progress, avoid division by zero
1645 if ($products == 0) {
1646 $result->{'progress'}= 0;
1647 } else {
1648 $result->{'progress'}= int($installed * 100 / $products);
1649 }
1651 # Set updates in job queue
1652 if ((not $error) && (not $installing) && ($installed)) {
1653 $act_status = "installed - ".join(", ", @installed_list);
1654 }
1655 if ($error) {
1656 $act_status = "error - ".join(", ", @error_list);
1657 }
1658 if ($progress ne $result->{'progress'} ) {
1659 # Updating progress and result
1660 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1661 my $update_res = $job_db->update_dbentry($update_statement);
1662 }
1663 if ($progress eq 100) {
1664 # Updateing status
1665 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1666 if ($error) {
1667 $done_statement .= "status='error'";
1668 } else {
1669 $done_statement .= "status='done'";
1670 }
1671 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1672 my $done_res = $job_db->update_dbentry($done_statement);
1673 }
1676 }
1677 }
1679 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1680 }
1683 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1684 sub watch_for_modified_jobs {
1685 my ($kernel,$heap) = @_[KERNEL, HEAP];
1687 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1688 my $res = $job_db->select_dbentry( $sql_statement );
1690 # if db contains no jobs which should be update, do nothing
1691 if (keys %$res != 0) {
1693 if ($job_synchronization eq "true") {
1694 # make out of the db result a gosa-si message
1695 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1697 # update all other SI-server
1698 &inform_all_other_si_server($update_msg);
1699 }
1701 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1702 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1703 $res = $job_db->update_dbentry($sql_statement);
1704 }
1706 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1707 }
1710 sub watch_for_new_jobs {
1711 if($watch_for_new_jobs_in_progress == 0) {
1712 $watch_for_new_jobs_in_progress = 1;
1713 my ($kernel,$heap) = @_[KERNEL, HEAP];
1715 # check gosa job quaeue for jobs with executable timestamp
1716 my $timestamp = &get_time();
1717 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1718 my $res = $job_db->exec_statement( $sql_statement );
1720 # Merge all new jobs that would do the same actions
1721 my @drops;
1722 my $hits;
1723 foreach my $hit (reverse @{$res} ) {
1724 my $macaddress= lc @{$hit}[8];
1725 my $headertag= @{$hit}[5];
1726 if(
1727 defined($hits->{$macaddress}) &&
1728 defined($hits->{$macaddress}->{$headertag}) &&
1729 defined($hits->{$macaddress}->{$headertag}[0])
1730 ) {
1731 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1732 }
1733 $hits->{$macaddress}->{$headertag}= $hit;
1734 }
1736 # Delete new jobs with a matching job in state 'processing'
1737 foreach my $macaddress (keys %{$hits}) {
1738 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1739 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1740 if(defined($jobdb_id)) {
1741 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1742 my $res = $job_db->exec_statement( $sql_statement );
1743 foreach my $hit (@{$res}) {
1744 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1745 }
1746 } else {
1747 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1748 }
1749 }
1750 }
1752 # Commit deletion
1753 $job_db->exec_statementlist(\@drops);
1755 # Look for new jobs that could be executed
1756 foreach my $macaddress (keys %{$hits}) {
1758 # Look if there is an executing job
1759 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1760 my $res = $job_db->exec_statement( $sql_statement );
1762 # Skip new jobs for host if there is a processing job
1763 if(defined($res) and defined @{$res}[0]) {
1764 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1765 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1766 if(@{$row}[5] eq 'trigger_action_reinstall') {
1767 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1768 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1769 if(defined($res_2) and defined @{$res_2}[0]) {
1770 # Set status from goto-activation to 'waiting' and update timestamp
1771 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1772 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1773 }
1774 }
1775 next;
1776 }
1778 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1779 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1780 if(defined($jobdb_id)) {
1781 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1783 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1784 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1785 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1787 # expect macaddress is unique!!!!!!
1788 my $target = $res_hash->{1}->{hostname};
1790 # change header
1791 $job_msg =~ s/<header>job_/<header>gosa_/;
1793 # add sqlite_id
1794 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1796 $job_msg =~ /<header>(\S+)<\/header>/;
1797 my $header = $1 ;
1798 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1800 # update status in job queue to 'processing'
1801 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1802 my $res = $job_db->update_dbentry($sql_statement);
1803 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1805 # We don't want parallel processing
1806 last;
1807 }
1808 }
1809 }
1811 $watch_for_new_jobs_in_progress = 0;
1812 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1813 }
1814 }
1817 sub watch_for_new_messages {
1818 my ($kernel,$heap) = @_[KERNEL, HEAP];
1819 my @coll_user_msg; # collection list of outgoing messages
1821 # check messaging_db for new incoming messages with executable timestamp
1822 my $timestamp = &get_time();
1823 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1824 my $res = $messaging_db->exec_statement( $sql_statement );
1825 foreach my $hit (@{$res}) {
1827 # create outgoing messages
1828 my $message_to = @{$hit}[3];
1829 # translate message_to to plain login name
1830 my @message_to_l = split(/,/, $message_to);
1831 my %receiver_h;
1832 foreach my $receiver (@message_to_l) {
1833 if ($receiver =~ /^u_([\s\S]*)$/) {
1834 $receiver_h{$1} = 0;
1835 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1836 my $group_name = $1;
1837 # fetch all group members from ldap and add them to receiver hash
1838 my $ldap_handle = &get_ldap_handle();
1839 if (defined $ldap_handle) {
1840 my $mesg = $ldap_handle->search(
1841 base => $ldap_base,
1842 scope => 'sub',
1843 attrs => ['memberUid'],
1844 filter => "cn=$group_name",
1845 );
1846 if ($mesg->count) {
1847 my @entries = $mesg->entries;
1848 foreach my $entry (@entries) {
1849 my @receivers= $entry->get_value("memberUid");
1850 foreach my $receiver (@receivers) {
1851 $receiver_h{$receiver} = 0;
1852 }
1853 }
1854 }
1855 # translating errors ?
1856 if ($mesg->code) {
1857 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1858 }
1859 # ldap handle error ?
1860 } else {
1861 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1862 }
1863 } else {
1864 my $sbjct = &encode_base64(@{$hit}[1]);
1865 my $msg = &encode_base64(@{$hit}[7]);
1866 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1867 }
1868 }
1869 my @receiver_l = keys(%receiver_h);
1871 my $message_id = @{$hit}[0];
1873 #add each outgoing msg to messaging_db
1874 my $receiver;
1875 foreach $receiver (@receiver_l) {
1876 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1877 "VALUES ('".
1878 $message_id."', '". # id
1879 @{$hit}[1]."', '". # subject
1880 @{$hit}[2]."', '". # message_from
1881 $receiver."', '". # message_to
1882 "none"."', '". # flag
1883 "out"."', '". # direction
1884 @{$hit}[6]."', '". # delivery_time
1885 @{$hit}[7]."', '". # message
1886 $timestamp."'". # timestamp
1887 ")";
1888 &daemon_log("M DEBUG: $sql_statement", 1);
1889 my $res = $messaging_db->exec_statement($sql_statement);
1890 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1891 }
1893 # set incoming message to flag d=deliverd
1894 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1895 &daemon_log("M DEBUG: $sql_statement", 7);
1896 $res = $messaging_db->update_dbentry($sql_statement);
1897 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1898 }
1900 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1901 return;
1902 }
1904 sub watch_for_delivery_messages {
1905 my ($kernel, $heap) = @_[KERNEL, HEAP];
1907 # select outgoing messages
1908 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1909 #&daemon_log("0 DEBUG: $sql", 7);
1910 my $res = $messaging_db->exec_statement( $sql_statement );
1912 # build out msg for each usr
1913 foreach my $hit (@{$res}) {
1914 my $receiver = @{$hit}[3];
1915 my $msg_id = @{$hit}[0];
1916 my $subject = @{$hit}[1];
1917 my $message = @{$hit}[7];
1919 # resolve usr -> host where usr is logged in
1920 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1921 #&daemon_log("0 DEBUG: $sql", 7);
1922 my $res = $login_users_db->exec_statement($sql);
1924 # reciver is logged in nowhere
1925 if (not ref(@$res[0]) eq "ARRAY") { next; }
1927 my $send_succeed = 0;
1928 foreach my $hit (@$res) {
1929 my $receiver_host = @$hit[0];
1930 my $delivered2host = 0;
1931 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1933 # Looking for host in know_clients_db
1934 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1935 my $res = $known_clients_db->exec_statement($sql);
1937 # Host is known in known_clients_db
1938 if (ref(@$res[0]) eq "ARRAY") {
1939 my $receiver_key = @{@{$res}[0]}[2];
1940 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1941 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1942 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1943 if ($error == 0 ) {
1944 $send_succeed++ ;
1945 $delivered2host++ ;
1946 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1947 } else {
1948 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1949 }
1950 }
1952 # Message already send, do not need to do anything more, otherwise ...
1953 if ($delivered2host) { next;}
1955 # ...looking for host in foreign_clients_db
1956 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1957 $res = $foreign_clients_db->exec_statement($sql);
1959 # Host is known in foreign_clients_db
1960 if (ref(@$res[0]) eq "ARRAY") {
1961 my $registration_server = @{@{$res}[0]}[2];
1963 # Fetch encryption key for registration server
1964 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1965 my $res = $known_server_db->exec_statement($sql);
1966 if (ref(@$res[0]) eq "ARRAY") {
1967 my $registration_server_key = @{@{$res}[0]}[3];
1968 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1969 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1970 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1971 if ($error == 0 ) {
1972 $send_succeed++ ;
1973 $delivered2host++ ;
1974 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1975 } else {
1976 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1977 }
1979 } else {
1980 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1981 "registrated at server '$registration_server', ".
1982 "but no data available in known_server_db ", 1);
1983 }
1984 }
1986 if (not $delivered2host) {
1987 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1988 }
1989 }
1991 if ($send_succeed) {
1992 # set outgoing msg at db to deliverd
1993 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1994 my $res = $messaging_db->exec_statement($sql);
1995 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1996 } else {
1997 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
1998 }
1999 }
2001 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2002 return;
2003 }
2006 sub watch_for_done_messages {
2007 my ($kernel,$heap) = @_[KERNEL, HEAP];
2009 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2010 #&daemon_log("0 DEBUG: $sql", 7);
2011 my $res = $messaging_db->exec_statement($sql);
2013 foreach my $hit (@{$res}) {
2014 my $msg_id = @{$hit}[0];
2016 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2017 #&daemon_log("0 DEBUG: $sql", 7);
2018 my $res = $messaging_db->exec_statement($sql);
2020 # not all usr msgs have been seen till now
2021 if ( ref(@$res[0]) eq "ARRAY") { next; }
2023 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2024 #&daemon_log("0 DEBUG: $sql", 7);
2025 $res = $messaging_db->exec_statement($sql);
2027 }
2029 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2030 return;
2031 }
2034 sub watch_for_old_known_clients {
2035 my ($kernel,$heap) = @_[KERNEL, HEAP];
2037 my $sql_statement = "SELECT * FROM $known_clients_tn";
2038 my $res = $known_clients_db->select_dbentry( $sql_statement );
2040 my $act_time = int(&get_time());
2042 while ( my ($hit_num, $hit) = each %$res) {
2043 my $expired_timestamp = int($hit->{'timestamp'});
2044 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2045 my $dt = DateTime->new( year => $1,
2046 month => $2,
2047 day => $3,
2048 hour => $4,
2049 minute => $5,
2050 second => $6,
2051 );
2053 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2054 $expired_timestamp = $dt->ymd('').$dt->hms('');
2055 if ($act_time > $expired_timestamp) {
2056 my $hostname = $hit->{'hostname'};
2057 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2058 my $del_res = $known_clients_db->exec_statement($del_sql);
2060 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2061 }
2063 }
2065 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2066 }
2069 sub watch_for_next_tasks {
2070 my ($kernel,$heap) = @_[KERNEL, HEAP];
2072 my $sql = "SELECT * FROM $incoming_tn";
2073 my $res = $incoming_db->select_dbentry($sql);
2075 while ( my ($hit_num, $hit) = each %$res) {
2076 my $headertag = $hit->{'headertag'};
2077 if ($headertag =~ /^answer_(\d+)/) {
2078 # do not start processing, this message is for a still running POE::Wheel
2079 next;
2080 }
2081 my $message_id = $hit->{'id'};
2082 my $session_id = $hit->{'sessionid'};
2083 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2084 $kernel->yield('next_task', $hit);
2086 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2087 my $res = $incoming_db->exec_statement($sql);
2088 }
2090 $kernel->delay_set('watch_for_next_tasks', 1);
2091 }
2094 sub get_ldap_handle {
2095 my ($session_id) = @_;
2096 my $heap;
2097 my $ldap_handle;
2099 if (not defined $session_id ) { $session_id = 0 };
2100 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2102 if ($session_id == 0) {
2103 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2104 $ldap_handle = Net::LDAP->new( $ldap_uri );
2105 if (defined $ldap_handle) {
2106 $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!");
2107 } else {
2108 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2109 }
2111 } else {
2112 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2113 if( defined $session_reference ) {
2114 $heap = $session_reference->get_heap();
2115 }
2117 if (not defined $heap) {
2118 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2119 return;
2120 }
2122 # TODO: This "if" is nonsense, because it doesn't prove that the
2123 # used handle is still valid - or if we've to reconnect...
2124 #if (not exists $heap->{ldap_handle}) {
2125 $ldap_handle = Net::LDAP->new( $ldap_uri );
2126 $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!");
2127 $heap->{ldap_handle} = $ldap_handle;
2128 #}
2129 }
2130 return $ldap_handle;
2131 }
2134 sub change_fai_state {
2135 my ($st, $targets, $session_id) = @_;
2136 $session_id = 0 if not defined $session_id;
2137 # Set FAI state to localboot
2138 my %mapActions= (
2139 reboot => '',
2140 update => 'softupdate',
2141 localboot => 'localboot',
2142 reinstall => 'install',
2143 rescan => '',
2144 wake => '',
2145 memcheck => 'memcheck',
2146 sysinfo => 'sysinfo',
2147 install => 'install',
2148 );
2150 # Return if this is unknown
2151 if (!exists $mapActions{ $st }){
2152 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2153 return;
2154 }
2156 my $state= $mapActions{ $st };
2158 my $ldap_handle = &get_ldap_handle($session_id);
2159 if( defined($ldap_handle) ) {
2161 # Build search filter for hosts
2162 my $search= "(&(objectClass=GOhard)";
2163 foreach (@{$targets}){
2164 $search.= "(macAddress=$_)";
2165 }
2166 $search.= ")";
2168 # If there's any host inside of the search string, procress them
2169 if (!($search =~ /macAddress/)){
2170 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2171 return;
2172 }
2174 # Perform search for Unit Tag
2175 my $mesg = $ldap_handle->search(
2176 base => $ldap_base,
2177 scope => 'sub',
2178 attrs => ['dn', 'FAIstate', 'objectClass'],
2179 filter => "$search"
2180 );
2182 if ($mesg->count) {
2183 my @entries = $mesg->entries;
2184 if (0 == @entries) {
2185 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2186 }
2188 foreach my $entry (@entries) {
2189 # Only modify entry if it is not set to '$state'
2190 if ($entry->get_value("FAIstate") ne "$state"){
2191 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2192 my $result;
2193 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2194 if (exists $tmp{'FAIobject'}){
2195 if ($state eq ''){
2196 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2197 } else {
2198 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2199 }
2200 } elsif ($state ne ''){
2201 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2202 }
2204 # Errors?
2205 if ($result->code){
2206 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2207 }
2208 } else {
2209 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2210 }
2211 }
2212 } else {
2213 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2214 }
2216 # if no ldap handle defined
2217 } else {
2218 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2219 }
2221 return;
2222 }
2225 sub change_goto_state {
2226 my ($st, $targets, $session_id) = @_;
2227 $session_id = 0 if not defined $session_id;
2229 # Switch on or off?
2230 my $state= $st eq 'active' ? 'active': 'locked';
2232 my $ldap_handle = &get_ldap_handle($session_id);
2233 if( defined($ldap_handle) ) {
2235 # Build search filter for hosts
2236 my $search= "(&(objectClass=GOhard)";
2237 foreach (@{$targets}){
2238 $search.= "(macAddress=$_)";
2239 }
2240 $search.= ")";
2242 # If there's any host inside of the search string, procress them
2243 if (!($search =~ /macAddress/)){
2244 return;
2245 }
2247 # Perform search for Unit Tag
2248 my $mesg = $ldap_handle->search(
2249 base => $ldap_base,
2250 scope => 'sub',
2251 attrs => ['dn', 'gotoMode'],
2252 filter => "$search"
2253 );
2255 if ($mesg->count) {
2256 my @entries = $mesg->entries;
2257 foreach my $entry (@entries) {
2259 # Only modify entry if it is not set to '$state'
2260 if ($entry->get_value("gotoMode") ne $state){
2262 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2263 my $result;
2264 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2266 # Errors?
2267 if ($result->code){
2268 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2269 }
2271 }
2272 }
2273 } else {
2274 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2275 }
2277 }
2278 }
2281 sub run_recreate_packages_db {
2282 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2283 my $session_id = $session->ID;
2284 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2285 $kernel->yield('create_fai_release_db', $fai_release_tn);
2286 $kernel->yield('create_fai_server_db', $fai_server_tn);
2287 return;
2288 }
2291 sub run_create_fai_server_db {
2292 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2293 my $session_id = $session->ID;
2294 my $task = POE::Wheel::Run->new(
2295 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2296 StdoutEvent => "session_run_result",
2297 StderrEvent => "session_run_debug",
2298 CloseEvent => "session_run_done",
2299 );
2301 $heap->{task}->{ $task->ID } = $task;
2302 return;
2303 }
2306 sub create_fai_server_db {
2307 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2308 my $result;
2310 if (not defined $session_id) { $session_id = 0; }
2311 my $ldap_handle = &get_ldap_handle();
2312 if(defined($ldap_handle)) {
2313 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2314 my $mesg= $ldap_handle->search(
2315 base => $ldap_base,
2316 scope => 'sub',
2317 attrs => ['FAIrepository', 'gosaUnitTag'],
2318 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2319 );
2320 if($mesg->{'resultCode'} == 0 &&
2321 $mesg->count != 0) {
2322 foreach my $entry (@{$mesg->{entries}}) {
2323 if($entry->exists('FAIrepository')) {
2324 # Add an entry for each Repository configured for server
2325 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2326 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2327 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2328 $result= $fai_server_db->add_dbentry( {
2329 table => $table_name,
2330 primkey => ['server', 'fai_release', 'tag'],
2331 server => $tmp_url,
2332 fai_release => $tmp_release,
2333 sections => $tmp_sections,
2334 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2335 } );
2336 }
2337 }
2338 }
2339 }
2340 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2342 # TODO: Find a way to post the 'create_packages_list_db' event
2343 if(not defined($dont_create_packages_list)) {
2344 &create_packages_list_db(undef, undef, $session_id);
2345 }
2346 }
2348 $ldap_handle->disconnect;
2349 return $result;
2350 }
2353 sub run_create_fai_release_db {
2354 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2355 my $session_id = $session->ID;
2356 my $task = POE::Wheel::Run->new(
2357 Program => sub { &create_fai_release_db($table_name, $session_id) },
2358 StdoutEvent => "session_run_result",
2359 StderrEvent => "session_run_debug",
2360 CloseEvent => "session_run_done",
2361 );
2363 $heap->{task}->{ $task->ID } = $task;
2364 return;
2365 }
2368 sub create_fai_release_db {
2369 my ($table_name, $session_id) = @_;
2370 my $result;
2372 # used for logging
2373 if (not defined $session_id) { $session_id = 0; }
2375 my $ldap_handle = &get_ldap_handle();
2376 if(defined($ldap_handle)) {
2377 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2378 my $mesg= $ldap_handle->search(
2379 base => $ldap_base,
2380 scope => 'sub',
2381 attrs => [],
2382 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2383 );
2384 if($mesg->{'resultCode'} == 0 &&
2385 $mesg->count != 0) {
2386 # Walk through all possible FAI container ou's
2387 my @sql_list;
2388 my $timestamp= &get_time();
2389 foreach my $ou (@{$mesg->{entries}}) {
2390 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2391 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2392 my @tmp_array=get_fai_release_entries($tmp_classes);
2393 if(@tmp_array) {
2394 foreach my $entry (@tmp_array) {
2395 if(defined($entry) && ref($entry) eq 'HASH') {
2396 my $sql=
2397 "INSERT INTO $table_name "
2398 ."(timestamp, fai_release, class, type, state) VALUES ("
2399 .$timestamp.","
2400 ."'".$entry->{'release'}."',"
2401 ."'".$entry->{'class'}."',"
2402 ."'".$entry->{'type'}."',"
2403 ."'".$entry->{'state'}."')";
2404 push @sql_list, $sql;
2405 }
2406 }
2407 }
2408 }
2409 }
2411 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2412 if(@sql_list) {
2413 unshift @sql_list, "DELETE FROM $table_name";
2414 $fai_release_db->exec_statementlist(\@sql_list);
2415 }
2416 daemon_log("$session_id DEBUG: Done with inserting",7);
2417 }
2418 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2419 }
2420 $ldap_handle->disconnect;
2421 return $result;
2422 }
2424 sub get_fai_types {
2425 my $tmp_classes = shift || return undef;
2426 my @result;
2428 foreach my $type(keys %{$tmp_classes}) {
2429 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2430 my $entry = {
2431 type => $type,
2432 state => $tmp_classes->{$type}[0],
2433 };
2434 push @result, $entry;
2435 }
2436 }
2438 return @result;
2439 }
2441 sub get_fai_state {
2442 my $result = "";
2443 my $tmp_classes = shift || return $result;
2445 foreach my $type(keys %{$tmp_classes}) {
2446 if(defined($tmp_classes->{$type}[0])) {
2447 $result = $tmp_classes->{$type}[0];
2449 # State is equal for all types in class
2450 last;
2451 }
2452 }
2454 return $result;
2455 }
2457 sub resolve_fai_classes {
2458 my ($fai_base, $ldap_handle, $session_id) = @_;
2459 if (not defined $session_id) { $session_id = 0; }
2460 my $result;
2461 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2462 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2463 my $fai_classes;
2465 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2466 my $mesg= $ldap_handle->search(
2467 base => $fai_base,
2468 scope => 'sub',
2469 attrs => ['cn','objectClass','FAIstate'],
2470 filter => $fai_filter,
2471 );
2472 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2474 if($mesg->{'resultCode'} == 0 &&
2475 $mesg->count != 0) {
2476 foreach my $entry (@{$mesg->{entries}}) {
2477 if($entry->exists('cn')) {
2478 my $tmp_dn= $entry->dn();
2480 # Skip classname and ou dn parts for class
2481 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2483 # Skip classes without releases
2484 if((!defined($tmp_release)) || length($tmp_release)==0) {
2485 next;
2486 }
2488 my $tmp_cn= $entry->get_value('cn');
2489 my $tmp_state= $entry->get_value('FAIstate');
2491 my $tmp_type;
2492 # Get FAI type
2493 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2494 if(grep $_ eq $oclass, @possible_fai_classes) {
2495 $tmp_type= $oclass;
2496 last;
2497 }
2498 }
2500 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2501 # A Subrelease
2502 my @sub_releases = split(/,/, $tmp_release);
2504 # Walk through subreleases and build hash tree
2505 my $hash;
2506 while(my $tmp_sub_release = pop @sub_releases) {
2507 $hash .= "\{'$tmp_sub_release'\}->";
2508 }
2509 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2510 } else {
2511 # A branch, no subrelease
2512 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2513 }
2514 } elsif (!$entry->exists('cn')) {
2515 my $tmp_dn= $entry->dn();
2516 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2518 # Skip classes without releases
2519 if((!defined($tmp_release)) || length($tmp_release)==0) {
2520 next;
2521 }
2523 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2524 # A Subrelease
2525 my @sub_releases= split(/,/, $tmp_release);
2527 # Walk through subreleases and build hash tree
2528 my $hash;
2529 while(my $tmp_sub_release = pop @sub_releases) {
2530 $hash .= "\{'$tmp_sub_release'\}->";
2531 }
2532 # Remove the last two characters
2533 chop($hash);
2534 chop($hash);
2536 eval('$fai_classes->'.$hash.'= {}');
2537 } else {
2538 # A branch, no subrelease
2539 if(!exists($fai_classes->{$tmp_release})) {
2540 $fai_classes->{$tmp_release} = {};
2541 }
2542 }
2543 }
2544 }
2546 # The hash is complete, now we can honor the copy-on-write based missing entries
2547 foreach my $release (keys %$fai_classes) {
2548 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2549 }
2550 }
2551 return $result;
2552 }
2554 sub apply_fai_inheritance {
2555 my $fai_classes = shift || return {};
2556 my $tmp_classes;
2558 # Get the classes from the branch
2559 foreach my $class (keys %{$fai_classes}) {
2560 # Skip subreleases
2561 if($class =~ /^ou=.*$/) {
2562 next;
2563 } else {
2564 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2565 }
2566 }
2568 # Apply to each subrelease
2569 foreach my $subrelease (keys %{$fai_classes}) {
2570 if($subrelease =~ /ou=/) {
2571 foreach my $tmp_class (keys %{$tmp_classes}) {
2572 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2573 $fai_classes->{$subrelease}->{$tmp_class} =
2574 deep_copy($tmp_classes->{$tmp_class});
2575 } else {
2576 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2577 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2578 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2579 deep_copy($tmp_classes->{$tmp_class}->{$type});
2580 }
2581 }
2582 }
2583 }
2584 }
2585 }
2587 # Find subreleases in deeper levels
2588 foreach my $subrelease (keys %{$fai_classes}) {
2589 if($subrelease =~ /ou=/) {
2590 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2591 if($subsubrelease =~ /ou=/) {
2592 apply_fai_inheritance($fai_classes->{$subrelease});
2593 }
2594 }
2595 }
2596 }
2598 return $fai_classes;
2599 }
2601 sub get_fai_release_entries {
2602 my $tmp_classes = shift || return;
2603 my $parent = shift || "";
2604 my @result = shift || ();
2606 foreach my $entry (keys %{$tmp_classes}) {
2607 if(defined($entry)) {
2608 if($entry =~ /^ou=.*$/) {
2609 my $release_name = $entry;
2610 $release_name =~ s/ou=//g;
2611 if(length($parent)>0) {
2612 $release_name = $parent."/".$release_name;
2613 }
2614 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2615 foreach my $bufentry(@bufentries) {
2616 push @result, $bufentry;
2617 }
2618 } else {
2619 my @types = get_fai_types($tmp_classes->{$entry});
2620 foreach my $type (@types) {
2621 push @result,
2622 {
2623 'class' => $entry,
2624 'type' => $type->{'type'},
2625 'release' => $parent,
2626 'state' => $type->{'state'},
2627 };
2628 }
2629 }
2630 }
2631 }
2633 return @result;
2634 }
2636 sub deep_copy {
2637 my $this = shift;
2638 if (not ref $this) {
2639 $this;
2640 } elsif (ref $this eq "ARRAY") {
2641 [map deep_copy($_), @$this];
2642 } elsif (ref $this eq "HASH") {
2643 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2644 } else { die "what type is $_?" }
2645 }
2648 sub session_run_result {
2649 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2650 $kernel->sig(CHLD => "child_reap");
2651 }
2653 sub session_run_debug {
2654 my $result = $_[ARG0];
2655 print STDERR "$result\n";
2656 }
2658 sub session_run_done {
2659 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2660 delete $heap->{task}->{$task_id};
2661 }
2664 sub create_sources_list {
2665 my $session_id = shift;
2666 my $ldap_handle = &main::get_ldap_handle;
2667 my $result="/tmp/gosa_si_tmp_sources_list";
2669 # Remove old file
2670 if(stat($result)) {
2671 unlink($result);
2672 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2673 }
2675 my $fh;
2676 open($fh, ">$result");
2677 if (not defined $fh) {
2678 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2679 return undef;
2680 }
2681 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2682 my $mesg=$ldap_handle->search(
2683 base => $main::ldap_server_dn,
2684 scope => 'base',
2685 attrs => 'FAIrepository',
2686 filter => 'objectClass=FAIrepositoryServer'
2687 );
2688 if($mesg->count) {
2689 foreach my $entry(@{$mesg->{'entries'}}) {
2690 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2691 my ($server, $tag, $release, $sections)= split /\|/, $value;
2692 my $line = "deb $server $release";
2693 $sections =~ s/,/ /g;
2694 $line.= " $sections";
2695 print $fh $line."\n";
2696 }
2697 }
2698 }
2699 } else {
2700 if (defined $main::ldap_server_dn){
2701 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2702 } else {
2703 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2704 }
2705 }
2706 close($fh);
2708 return $result;
2709 }
2712 sub run_create_packages_list_db {
2713 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2714 my $session_id = $session->ID;
2716 my $task = POE::Wheel::Run->new(
2717 Priority => +20,
2718 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2719 StdoutEvent => "session_run_result",
2720 StderrEvent => "session_run_debug",
2721 CloseEvent => "session_run_done",
2722 );
2723 $heap->{task}->{ $task->ID } = $task;
2724 }
2727 sub create_packages_list_db {
2728 my ($ldap_handle, $sources_file, $session_id) = @_;
2730 # it should not be possible to trigger a recreation of packages_list_db
2731 # while packages_list_db is under construction, so set flag packages_list_under_construction
2732 # which is tested befor recreation can be started
2733 if (-r $packages_list_under_construction) {
2734 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2735 return;
2736 } else {
2737 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2738 # set packages_list_under_construction to true
2739 system("touch $packages_list_under_construction");
2740 @packages_list_statements=();
2741 }
2743 if (not defined $session_id) { $session_id = 0; }
2744 if (not defined $ldap_handle) {
2745 $ldap_handle= &get_ldap_handle();
2747 if (not defined $ldap_handle) {
2748 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2749 unlink($packages_list_under_construction);
2750 return;
2751 }
2752 }
2753 if (not defined $sources_file) {
2754 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2755 $sources_file = &create_sources_list($session_id);
2756 }
2758 if (not defined $sources_file) {
2759 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2760 unlink($packages_list_under_construction);
2761 return;
2762 }
2764 my $line;
2766 open(CONFIG, "<$sources_file") or do {
2767 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2768 unlink($packages_list_under_construction);
2769 return;
2770 };
2772 # Read lines
2773 while ($line = <CONFIG>){
2774 # Unify
2775 chop($line);
2776 $line =~ s/^\s+//;
2777 $line =~ s/^\s+/ /;
2779 # Strip comments
2780 $line =~ s/#.*$//g;
2782 # Skip empty lines
2783 if ($line =~ /^\s*$/){
2784 next;
2785 }
2787 # Interpret deb line
2788 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2789 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2790 my $section;
2791 foreach $section (split(' ', $sections)){
2792 &parse_package_info( $baseurl, $dist, $section, $session_id );
2793 }
2794 }
2795 }
2797 close (CONFIG);
2799 if(keys(%repo_dirs)) {
2800 find(\&cleanup_and_extract, keys( %repo_dirs ));
2801 &main::strip_packages_list_statements();
2802 $packages_list_db->exec_statementlist(\@packages_list_statements);
2803 }
2804 unlink($packages_list_under_construction);
2805 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2806 return;
2807 }
2809 # This function should do some intensive task to minimize the db-traffic
2810 sub strip_packages_list_statements {
2811 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2812 my @new_statement_list=();
2813 my $hash;
2814 my $insert_hash;
2815 my $update_hash;
2816 my $delete_hash;
2817 my $known_packages_hash;
2818 my $local_timestamp=get_time();
2820 foreach my $existing_entry (@existing_entries) {
2821 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2822 }
2824 foreach my $statement (@packages_list_statements) {
2825 if($statement =~ /^INSERT/i) {
2826 # Assign the values from the insert statement
2827 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2828 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2829 if(exists($hash->{$distribution}->{$package}->{$version})) {
2830 # If section or description has changed, update the DB
2831 if(
2832 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2833 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2834 ) {
2835 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2836 } else {
2837 # package is already present in database. cache this knowledge for later use
2838 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2839 }
2840 } else {
2841 # Insert a non-existing entry to db
2842 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2843 }
2844 } elsif ($statement =~ /^UPDATE/i) {
2845 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2846 /^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;
2847 foreach my $distribution (keys %{$hash}) {
2848 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2849 # update the insertion hash to execute only one query per package (insert instead insert+update)
2850 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2851 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2852 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2853 my $section;
2854 my $description;
2855 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2856 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2857 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2858 }
2859 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2860 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2861 }
2862 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2863 }
2864 }
2865 }
2866 }
2867 }
2869 # Check for orphaned entries
2870 foreach my $existing_entry (@existing_entries) {
2871 my $distribution= @{$existing_entry}[0];
2872 my $package= @{$existing_entry}[1];
2873 my $version= @{$existing_entry}[2];
2874 my $section= @{$existing_entry}[3];
2876 if(
2877 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2878 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2879 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2880 ) {
2881 next;
2882 } else {
2883 # Insert entry to delete hash
2884 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2885 }
2886 }
2888 # unroll the insert hash
2889 foreach my $distribution (keys %{$insert_hash}) {
2890 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2891 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2892 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2893 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2894 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2895 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2896 ."'$local_timestamp')";
2897 }
2898 }
2899 }
2901 # unroll the update hash
2902 foreach my $distribution (keys %{$update_hash}) {
2903 foreach my $package (keys %{$update_hash->{$distribution}}) {
2904 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2905 my $set = "";
2906 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2907 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2908 }
2909 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2910 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2911 }
2912 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2913 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2914 }
2915 if(defined($set) and length($set) > 0) {
2916 $set .= "timestamp = '$local_timestamp'";
2917 } else {
2918 next;
2919 }
2920 push @new_statement_list,
2921 "UPDATE $main::packages_list_tn SET $set WHERE"
2922 ." distribution = '$distribution'"
2923 ." AND package = '$package'"
2924 ." AND version = '$version'";
2925 }
2926 }
2927 }
2929 # unroll the delete hash
2930 foreach my $distribution (keys %{$delete_hash}) {
2931 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2932 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2933 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2934 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
2935 }
2936 }
2937 }
2939 @packages_list_statements = @new_statement_list;
2940 }
2943 sub parse_package_info {
2944 my ($baseurl, $dist, $section, $session_id)= @_;
2945 my ($package);
2946 if (not defined $session_id) { $session_id = 0; }
2947 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2948 $repo_dirs{ "${repo_path}/pool" } = 1;
2950 foreach $package ("Packages.gz"){
2951 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2952 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2953 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2954 }
2956 }
2959 sub get_package {
2960 my ($url, $dest, $session_id)= @_;
2961 if (not defined $session_id) { $session_id = 0; }
2963 my $tpath = dirname($dest);
2964 -d "$tpath" || mkpath "$tpath";
2966 # This is ugly, but I've no time to take a look at "how it works in perl"
2967 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2968 system("gunzip -cd '$dest' > '$dest.in'");
2969 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2970 unlink($dest);
2971 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2972 } else {
2973 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2974 }
2975 return 0;
2976 }
2979 sub parse_package {
2980 my ($path, $dist, $srv_path, $session_id)= @_;
2981 if (not defined $session_id) { $session_id = 0;}
2982 my ($package, $version, $section, $description);
2983 my $PACKAGES;
2984 my $timestamp = &get_time();
2986 if(not stat("$path.in")) {
2987 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2988 return;
2989 }
2991 open($PACKAGES, "<$path.in");
2992 if(not defined($PACKAGES)) {
2993 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2994 return;
2995 }
2997 # Read lines
2998 while (<$PACKAGES>){
2999 my $line = $_;
3000 # Unify
3001 chop($line);
3003 # Use empty lines as a trigger
3004 if ($line =~ /^\s*$/){
3005 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3006 push(@packages_list_statements, $sql);
3007 $package = "none";
3008 $version = "none";
3009 $section = "none";
3010 $description = "none";
3011 next;
3012 }
3014 # Trigger for package name
3015 if ($line =~ /^Package:\s/){
3016 ($package)= ($line =~ /^Package: (.*)$/);
3017 next;
3018 }
3020 # Trigger for version
3021 if ($line =~ /^Version:\s/){
3022 ($version)= ($line =~ /^Version: (.*)$/);
3023 next;
3024 }
3026 # Trigger for description
3027 if ($line =~ /^Description:\s/){
3028 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3029 next;
3030 }
3032 # Trigger for section
3033 if ($line =~ /^Section:\s/){
3034 ($section)= ($line =~ /^Section: (.*)$/);
3035 next;
3036 }
3038 # Trigger for filename
3039 if ($line =~ /^Filename:\s/){
3040 my ($filename) = ($line =~ /^Filename: (.*)$/);
3041 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3042 next;
3043 }
3044 }
3046 close( $PACKAGES );
3047 unlink( "$path.in" );
3048 }
3051 sub store_fileinfo {
3052 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3054 my %fileinfo = (
3055 'package' => $package,
3056 'dist' => $dist,
3057 'version' => $vers,
3058 );
3060 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3061 }
3064 sub cleanup_and_extract {
3065 my $fileinfo = $repo_files{ $File::Find::name };
3067 if( defined $fileinfo ) {
3068 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3069 my $sql;
3070 my $package = $fileinfo->{ 'package' };
3071 my $newver = $fileinfo->{ 'version' };
3073 mkpath($dir);
3074 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3076 if( -f "$dir/DEBIAN/templates" ) {
3078 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3080 my $tmpl= ""; {
3081 local $/=undef;
3082 open FILE, "$dir/DEBIAN/templates";
3083 $tmpl = &encode_base64(<FILE>);
3084 close FILE;
3085 }
3086 rmtree("$dir/DEBIAN/templates");
3088 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3089 push @packages_list_statements, $sql;
3090 }
3091 }
3093 return;
3094 }
3097 sub register_at_foreign_servers {
3098 my ($kernel) = $_[KERNEL];
3100 # hole alle bekannten server aus known_server_db
3101 my $server_sql = "SELECT * FROM $known_server_tn";
3102 my $server_res = $known_server_db->exec_statement($server_sql);
3104 # no entries in known_server_db
3105 if (not ref(@$server_res[0]) eq "ARRAY") {
3106 # TODO
3107 }
3109 # detect already connected clients
3110 my $client_sql = "SELECT * FROM $known_clients_tn";
3111 my $client_res = $known_clients_db->exec_statement($client_sql);
3113 # send my server details to all other gosa-si-server within the network
3114 foreach my $hit (@$server_res) {
3115 my $hostname = @$hit[0];
3116 my $hostkey = &create_passwd;
3118 # add already connected clients to registration message
3119 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3120 &add_content2xml_hash($myhash, 'key', $hostkey);
3121 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3123 # add locally loaded gosa-si modules to registration message
3124 my $loaded_modules = {};
3125 while (my ($package, $pck_info) = each %$known_modules) {
3126 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3127 foreach my $act_module (keys(%{@$pck_info[2]})) {
3128 $loaded_modules->{$act_module} = "";
3129 }
3130 }
3132 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3134 # add macaddress to registration message
3135 my ($host_ip, $host_port) = split(/:/, $hostname);
3136 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3137 my $network_interface= &get_interface_for_ip($local_ip);
3138 my $host_mac = &get_mac_for_interface($network_interface);
3139 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3141 # build registration message and send it
3142 my $foreign_server_msg = &create_xml_string($myhash);
3143 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3144 }
3146 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3147 return;
3148 }
3151 #==== MAIN = main ==============================================================
3152 # parse commandline options
3153 Getopt::Long::Configure( "bundling" );
3154 GetOptions("h|help" => \&usage,
3155 "c|config=s" => \$cfg_file,
3156 "f|foreground" => \$foreground,
3157 "v|verbose+" => \$verbose,
3158 "no-arp+" => \$no_arp,
3159 );
3161 # read and set config parameters
3162 &check_cmdline_param ;
3163 &read_configfile($cfg_file, %cfg_defaults);
3164 &check_pid;
3166 $SIG{CHLD} = 'IGNORE';
3168 # forward error messages to logfile
3169 if( ! $foreground ) {
3170 open( STDIN, '+>/dev/null' );
3171 open( STDOUT, '+>&STDIN' );
3172 open( STDERR, '+>&STDIN' );
3173 }
3175 # Just fork, if we are not in foreground mode
3176 if( ! $foreground ) {
3177 chdir '/' or die "Can't chdir to /: $!";
3178 $pid = fork;
3179 setsid or die "Can't start a new session: $!";
3180 umask 0;
3181 } else {
3182 $pid = $$;
3183 }
3185 # Do something useful - put our PID into the pid_file
3186 if( 0 != $pid ) {
3187 open( LOCK_FILE, ">$pid_file" );
3188 print LOCK_FILE "$pid\n";
3189 close( LOCK_FILE );
3190 if( !$foreground ) {
3191 exit( 0 )
3192 };
3193 }
3195 # parse head url and revision from svn
3196 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3197 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3198 $server_headURL = defined $1 ? $1 : 'unknown' ;
3199 $server_revision = defined $2 ? $2 : 'unknown' ;
3200 if ($server_headURL =~ /\/tag\// ||
3201 $server_headURL =~ /\/branches\// ) {
3202 $server_status = "stable";
3203 } else {
3204 $server_status = "developmental" ;
3205 }
3207 # Prepare log file
3208 $root_uid = getpwnam('root');
3209 $adm_gid = getgrnam('adm');
3210 chmod(0640, $log_file);
3211 chown($root_uid, $adm_gid, $log_file);
3212 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3214 daemon_log(" ", 1);
3215 daemon_log("$0 started!", 1);
3216 daemon_log("status: $server_status", 1);
3217 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3219 {
3220 no strict "refs";
3222 if ($db_module eq "DBmysql") {
3223 # connect to incoming_db
3224 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3226 # connect to gosa-si job queue
3227 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3229 # connect to known_clients_db
3230 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3232 # connect to foreign_clients_db
3233 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3235 # connect to known_server_db
3236 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3238 # connect to login_usr_db
3239 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3241 # connect to fai_server_db
3242 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3244 # connect to fai_release_db
3245 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3247 # connect to packages_list_db
3248 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3250 # connect to messaging_db
3251 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3253 } elsif ($db_module eq "DBsqlite") {
3254 # connect to incoming_db
3255 unlink($incoming_file_name);
3256 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3258 # connect to gosa-si job queue
3259 unlink($job_queue_file_name); ## just for debugging
3260 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3261 chmod(0660, $job_queue_file_name);
3262 chown($root_uid, $adm_gid, $job_queue_file_name);
3264 # connect to known_clients_db
3265 unlink($known_clients_file_name); ## just for debugging
3266 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3267 chmod(0660, $known_clients_file_name);
3268 chown($root_uid, $adm_gid, $known_clients_file_name);
3270 # connect to foreign_clients_db
3271 unlink($foreign_clients_file_name);
3272 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3273 chmod(0660, $foreign_clients_file_name);
3274 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3276 # connect to known_server_db
3277 unlink($known_server_file_name);
3278 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3279 chmod(0660, $known_server_file_name);
3280 chown($root_uid, $adm_gid, $known_server_file_name);
3282 # connect to login_usr_db
3283 unlink($login_users_file_name);
3284 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3285 chmod(0660, $login_users_file_name);
3286 chown($root_uid, $adm_gid, $login_users_file_name);
3288 # connect to fai_server_db
3289 unlink($fai_server_file_name);
3290 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3291 chmod(0660, $fai_server_file_name);
3292 chown($root_uid, $adm_gid, $fai_server_file_name);
3294 # connect to fai_release_db
3295 unlink($fai_release_file_name);
3296 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3297 chmod(0660, $fai_release_file_name);
3298 chown($root_uid, $adm_gid, $fai_release_file_name);
3300 # connect to packages_list_db
3301 #unlink($packages_list_file_name);
3302 unlink($packages_list_under_construction);
3303 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3304 chmod(0660, $packages_list_file_name);
3305 chown($root_uid, $adm_gid, $packages_list_file_name);
3307 # connect to messaging_db
3308 unlink($messaging_file_name);
3309 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3310 chmod(0660, $messaging_file_name);
3311 chown($root_uid, $adm_gid, $messaging_file_name);
3312 }
3313 }
3315 # Creating tables
3316 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3317 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3318 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3319 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3320 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3321 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3322 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3323 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3324 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3325 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3328 # create xml object used for en/decrypting
3329 $xml = new XML::Simple();
3332 # foreign servers
3333 my @foreign_server_list;
3335 # add foreign server from cfg file
3336 if ($foreign_server_string ne "") {
3337 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3338 foreach my $foreign_server (@cfg_foreign_server_list) {
3339 push(@foreign_server_list, $foreign_server);
3340 }
3342 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3343 }
3345 # Perform a DNS lookup for server registration if flag is true
3346 if ($dns_lookup eq "true") {
3347 # Add foreign server from dns
3348 my @tmp_servers;
3349 if (not $server_domain) {
3350 # Try our DNS Searchlist
3351 for my $domain(get_dns_domains()) {
3352 chomp($domain);
3353 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3354 if(@$tmp_domains) {
3355 for my $tmp_server(@$tmp_domains) {
3356 push @tmp_servers, $tmp_server;
3357 }
3358 }
3359 }
3360 if(@tmp_servers && length(@tmp_servers)==0) {
3361 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3362 }
3363 } else {
3364 @tmp_servers = &get_server_addresses($server_domain);
3365 if( 0 == @tmp_servers ) {
3366 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3367 }
3368 }
3370 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3372 foreach my $server (@tmp_servers) {
3373 unshift(@foreign_server_list, $server);
3374 }
3375 } else {
3376 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3377 }
3380 # eliminate duplicate entries
3381 @foreign_server_list = &del_doubles(@foreign_server_list);
3382 my $all_foreign_server = join(", ", @foreign_server_list);
3383 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3385 # add all found foreign servers to known_server
3386 my $act_timestamp = &get_time();
3387 foreach my $foreign_server (@foreign_server_list) {
3389 # do not add myself to known_server_db
3390 if (&is_local($foreign_server)) { next; }
3391 ######################################
3393 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3394 primkey=>['hostname'],
3395 hostname=>$foreign_server,
3396 macaddress=>"",
3397 status=>'not_jet_registered',
3398 hostkey=>"none",
3399 loaded_modules => "none",
3400 timestamp=>$act_timestamp,
3401 } );
3402 }
3405 # Import all modules
3406 &import_modules;
3408 # Check wether all modules are gosa-si valid passwd check
3409 &password_check;
3411 # Prepare for using Opsi
3412 if ($opsi_enabled eq "true") {
3413 use JSON::RPC::Client;
3414 use XML::Quote qw(:all);
3415 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3416 $opsi_client = new JSON::RPC::Client;
3417 }
3420 POE::Component::Server::TCP->new(
3421 Alias => "TCP_SERVER",
3422 Port => $server_port,
3423 ClientInput => sub {
3424 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3425 my $session_id = $session->ID;
3426 my $remote_ip = $heap->{'remote_ip'};
3427 push(@msgs_to_decrypt, $input);
3428 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3429 $kernel->yield("msg_to_decrypt");
3430 },
3431 InlineStates => {
3432 msg_to_decrypt => \&msg_to_decrypt,
3433 next_task => \&next_task,
3434 task_result => \&handle_task_result,
3435 task_done => \&handle_task_done,
3436 task_debug => \&handle_task_debug,
3437 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3438 }
3439 );
3441 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3443 # create session for repeatedly checking the job queue for jobs
3444 POE::Session->create(
3445 inline_states => {
3446 _start => \&session_start,
3447 register_at_foreign_servers => \®ister_at_foreign_servers,
3448 sig_handler => \&sig_handler,
3449 next_task => \&next_task,
3450 task_result => \&handle_task_result,
3451 task_done => \&handle_task_done,
3452 task_debug => \&handle_task_debug,
3453 watch_for_next_tasks => \&watch_for_next_tasks,
3454 watch_for_new_messages => \&watch_for_new_messages,
3455 watch_for_delivery_messages => \&watch_for_delivery_messages,
3456 watch_for_done_messages => \&watch_for_done_messages,
3457 watch_for_new_jobs => \&watch_for_new_jobs,
3458 watch_for_modified_jobs => \&watch_for_modified_jobs,
3459 watch_for_done_jobs => \&watch_for_done_jobs,
3460 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3461 watch_for_old_known_clients => \&watch_for_old_known_clients,
3462 create_packages_list_db => \&run_create_packages_list_db,
3463 create_fai_server_db => \&run_create_fai_server_db,
3464 create_fai_release_db => \&run_create_fai_release_db,
3465 recreate_packages_db => \&run_recreate_packages_db,
3466 session_run_result => \&session_run_result,
3467 session_run_debug => \&session_run_debug,
3468 session_run_done => \&session_run_done,
3469 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3470 }
3471 );
3474 POE::Kernel->run();
3475 exit;