ad0e99fa3d7590598a1719a7e7e39bca86baf559
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} ) { 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} ) { 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} ) { 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 }
604 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
605 $source =~ /^GOSA$/i ) {
606 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
607 }
609 ##############
610 # check target
611 my $target_l = $msg_hash->{'target'};
612 if( 0 == @{$target_l} ) {
613 die "no targets specified";
614 }
615 foreach my $target (@$target_l) {
616 if( 0 == length $target) {
617 die "target has length 0";
618 }
619 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
620 $target =~ /^GOSA$/i ||
621 $target =~ /^\*$/ ||
622 $target =~ /KNOWN_SERVER/i ||
623 $target =~ /JOBDB/i ||
624 $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 ){
625 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
626 }
627 }
628 };
629 if($@) {
630 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
631 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
632 $msg_hash = undef;
633 }
635 return ($msg_hash);
636 }
639 sub input_from_known_server {
640 my ($input, $remote_ip, $session_id) = @_ ;
641 my ($msg, $msg_hash, $module);
643 my $sql_statement= "SELECT * FROM known_server";
644 my $query_res = $known_server_db->select_dbentry( $sql_statement );
646 while( my ($hit_num, $hit) = each %{ $query_res } ) {
647 my $host_name = $hit->{hostname};
648 if( not $host_name =~ "^$remote_ip") {
649 next;
650 }
651 my $host_key = $hit->{hostkey};
652 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
653 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
655 # check if module can open msg envelope with module key
656 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
657 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
658 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
659 daemon_log("$@", 8);
660 next;
661 }
662 else {
663 $msg = $tmp_msg;
664 $msg_hash = $tmp_msg_hash;
665 $module = "ServerPackages";
666 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
667 last;
668 }
669 }
671 if( (!$msg) || (!$msg_hash) || (!$module) ) {
672 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
673 }
675 return ($msg, $msg_hash, $module);
676 }
679 sub input_from_known_client {
680 my ($input, $remote_ip, $session_id) = @_ ;
681 my ($msg, $msg_hash, $module);
683 my $sql_statement= "SELECT * FROM known_clients";
684 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
685 while( my ($hit_num, $hit) = each %{ $query_res } ) {
686 my $host_name = $hit->{hostname};
687 if( not $host_name =~ /^$remote_ip:\d*$/) {
688 next;
689 }
690 my $host_key = $hit->{hostkey};
691 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
692 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
694 # check if module can open msg envelope with module key
695 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
697 if( (!$msg) || (!$msg_hash) ) {
698 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
699 &daemon_log("$@", 8);
700 next;
701 }
702 else {
703 $module = "ClientPackages";
704 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
705 last;
706 }
707 }
709 if( (!$msg) || (!$msg_hash) || (!$module) ) {
710 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
711 }
713 return ($msg, $msg_hash, $module);
714 }
717 sub input_from_unknown_host {
718 no strict "refs";
719 my ($input, $session_id) = @_ ;
720 my ($msg, $msg_hash, $module);
721 my $error_string;
723 my %act_modules = %$known_modules;
725 while( my ($mod, $info) = each(%act_modules)) {
727 # check a key exists for this module
728 my $module_key = ${$mod."_key"};
729 if( not defined $module_key ) {
730 if( $mod eq 'ArpHandler' ) {
731 next;
732 }
733 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
734 next;
735 }
736 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
738 # check if module can open msg envelope with module key
739 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
740 if( (not defined $msg) || (not defined $msg_hash) ) {
741 next;
742 } else {
743 $module = $mod;
744 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
745 last;
746 }
747 }
749 if( (!$msg) || (!$msg_hash) || (!$module)) {
750 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
751 }
753 return ($msg, $msg_hash, $module);
754 }
757 sub create_ciphering {
758 my ($passwd) = @_;
759 if((!defined($passwd)) || length($passwd)==0) {
760 $passwd = "";
761 }
762 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
763 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
764 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
765 $my_cipher->set_iv($iv);
766 return $my_cipher;
767 }
770 sub encrypt_msg {
771 my ($msg, $key) = @_;
772 my $my_cipher = &create_ciphering($key);
773 my $len;
774 {
775 use bytes;
776 $len= 16-length($msg)%16;
777 }
778 $msg = "\0"x($len).$msg;
779 $msg = $my_cipher->encrypt($msg);
780 chomp($msg = &encode_base64($msg));
781 # there are no newlines allowed inside msg
782 $msg=~ s/\n//g;
783 return $msg;
784 }
787 sub decrypt_msg {
789 my ($msg, $key) = @_ ;
790 $msg = &decode_base64($msg);
791 my $my_cipher = &create_ciphering($key);
792 $msg = $my_cipher->decrypt($msg);
793 $msg =~ s/\0*//g;
794 return $msg;
795 }
798 sub get_encrypt_key {
799 my ($target) = @_ ;
800 my $encrypt_key;
801 my $error = 0;
803 # target can be in known_server
804 if( not defined $encrypt_key ) {
805 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
806 my $query_res = $known_server_db->select_dbentry( $sql_statement );
807 while( my ($hit_num, $hit) = each %{ $query_res } ) {
808 my $host_name = $hit->{hostname};
809 if( $host_name ne $target ) {
810 next;
811 }
812 $encrypt_key = $hit->{hostkey};
813 last;
814 }
815 }
817 # target can be in known_client
818 if( not defined $encrypt_key ) {
819 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
820 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
821 while( my ($hit_num, $hit) = each %{ $query_res } ) {
822 my $host_name = $hit->{hostname};
823 if( $host_name ne $target ) {
824 next;
825 }
826 $encrypt_key = $hit->{hostkey};
827 last;
828 }
829 }
831 return $encrypt_key;
832 }
835 #=== FUNCTION ================================================================
836 # NAME: open_socket
837 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
838 # [PeerPort] string necessary if port not appended by PeerAddr
839 # RETURNS: socket IO::Socket::INET
840 # DESCRIPTION: open a socket to PeerAddr
841 #===============================================================================
842 sub open_socket {
843 my ($PeerAddr, $PeerPort) = @_ ;
844 if(defined($PeerPort)){
845 $PeerAddr = $PeerAddr.":".$PeerPort;
846 }
847 my $socket;
848 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
849 Porto => "tcp",
850 Type => SOCK_STREAM,
851 Timeout => 5,
852 );
853 if(not defined $socket) {
854 return;
855 }
856 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
857 return $socket;
858 }
861 #sub get_local_ip_for_remote_ip {
862 # my $remote_ip= shift;
863 # my $result="0.0.0.0";
864 #
865 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
866 # if($remote_ip eq "127.0.0.1") {
867 # $result = "127.0.0.1";
868 # } else {
869 # my $PROC_NET_ROUTE= ('/proc/net/route');
870 #
871 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
872 # or die "Could not open $PROC_NET_ROUTE";
873 #
874 # my @ifs = <PROC_NET_ROUTE>;
875 #
876 # close(PROC_NET_ROUTE);
877 #
878 # # Eat header line
879 # shift @ifs;
880 # chomp @ifs;
881 # foreach my $line(@ifs) {
882 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
883 # my $destination;
884 # my $mask;
885 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
886 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
887 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
888 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
889 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
890 # # destination matches route, save mac and exit
891 # $result= &get_ip($Iface);
892 # last;
893 # }
894 # }
895 # }
896 # } else {
897 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
898 # }
899 # return $result;
900 #}
903 sub send_msg_to_target {
904 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
905 my $error = 0;
906 my $header;
907 my $timestamp = &get_time();
908 my $new_status;
909 my $act_status;
910 my ($sql_statement, $res);
912 if( $msg_header ) {
913 $header = "'$msg_header'-";
914 } else {
915 $header = "";
916 }
918 # Patch the source ip
919 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
920 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
921 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
922 }
924 # encrypt xml msg
925 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
927 # opensocket
928 my $socket = &open_socket($address);
929 if( !$socket ) {
930 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
931 $error++;
932 }
934 if( $error == 0 ) {
935 # send xml msg
936 print $socket $crypted_msg."\n";
938 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
939 daemon_log("$session_id DEBUG: message:\n$msg", 9);
941 }
943 # close socket in any case
944 if( $socket ) {
945 close $socket;
946 }
948 if( $error > 0 ) { $new_status = "down"; }
949 else { $new_status = $msg_header; }
952 # known_clients
953 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
954 $res = $known_clients_db->select_dbentry($sql_statement);
955 if( keys(%$res) == 1) {
956 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
957 if ($act_status eq "down" && $new_status eq "down") {
958 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
959 $res = $known_clients_db->del_dbentry($sql_statement);
960 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
961 } else {
962 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
963 $res = $known_clients_db->update_dbentry($sql_statement);
964 if($new_status eq "down"){
965 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
966 } else {
967 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
968 }
969 }
970 }
972 # known_server
973 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
974 $res = $known_server_db->select_dbentry($sql_statement);
975 if( keys(%$res) == 1) {
976 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
977 if ($act_status eq "down" && $new_status eq "down") {
978 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
979 $res = $known_server_db->del_dbentry($sql_statement);
980 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
981 }
982 else {
983 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
984 $res = $known_server_db->update_dbentry($sql_statement);
985 if($new_status eq "down"){
986 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
987 } else {
988 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
989 }
990 }
991 }
992 return $error;
993 }
996 sub update_jobdb_status_for_send_msgs {
997 my ($answer, $error) = @_;
998 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
999 my $jobdb_id = $1;
1001 # sending msg faild
1002 if( $error ) {
1003 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1004 my $sql_statement = "UPDATE $job_queue_tn ".
1005 "SET status='error', result='can not deliver msg, please consult log file' ".
1006 "WHERE id=$jobdb_id";
1007 my $res = $job_db->update_dbentry($sql_statement);
1008 }
1010 # sending msg was successful
1011 } else {
1012 my $sql_statement = "UPDATE $job_queue_tn ".
1013 "SET status='done' ".
1014 "WHERE id=$jobdb_id AND status='processed'";
1015 my $res = $job_db->update_dbentry($sql_statement);
1016 }
1017 }
1018 }
1021 sub sig_handler {
1022 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1023 daemon_log("0 INFO got signal '$signal'", 1);
1024 $kernel->sig_handled();
1025 return;
1026 }
1029 sub msg_to_decrypt {
1030 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1031 my $session_id = $session->ID;
1032 my ($msg, $msg_hash, $module);
1033 my $error = 0;
1035 # hole neue msg aus @msgs_to_decrypt
1036 my $next_msg = shift @msgs_to_decrypt;
1038 # msg is from a new client or gosa
1039 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1041 # msg is from a gosa-si-server
1042 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1043 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1044 }
1045 # msg is from a gosa-si-client
1046 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1047 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1048 }
1049 # an error occurred
1050 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1051 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1052 # could not understand a msg from its server the client cause a re-registering process
1053 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1054 "' to cause a re-registering of the client if necessary", 3);
1055 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1056 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1057 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1058 my $host_name = $hit->{'hostname'};
1059 my $host_key = $hit->{'hostkey'};
1060 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1061 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1062 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1063 }
1064 $error++;
1065 }
1068 my $header;
1069 my $target;
1070 my $source;
1071 my $done = 0;
1072 my $sql;
1073 my $res;
1075 # check whether this message should be processed here
1076 if ($error == 0) {
1077 $header = @{$msg_hash->{'header'}}[0];
1078 $target = @{$msg_hash->{'target'}}[0];
1079 $source = @{$msg_hash->{'source'}}[0];
1080 my $not_found_in_known_clients_db = 0;
1081 my $not_found_in_known_server_db = 0;
1082 my $not_found_in_foreign_clients_db = 0;
1083 my $local_address;
1084 my $local_mac;
1085 my ($target_ip, $target_port) = split(':', $target);
1087 # Determine the local ip address if target is an ip address
1088 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1089 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1090 } else {
1091 $local_address = $server_address;
1092 }
1094 # Determine the local mac address if target is a mac address
1095 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) {
1096 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1097 my $network_interface= &get_interface_for_ip($loc_ip);
1098 $local_mac = &get_mac_for_interface($network_interface);
1099 } else {
1100 $local_mac = $server_mac_address;
1101 }
1103 # target and source is equal to GOSA -> process here
1104 if (not $done) {
1105 if ($target eq "GOSA" && $source eq "GOSA") {
1106 $done = 1;
1107 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1108 }
1109 }
1111 # target is own address without forward_to_gosa-tag -> process here
1112 if (not $done) {
1113 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1114 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1115 $done = 1;
1116 if ($source eq "GOSA") {
1117 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1118 }
1119 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1120 }
1121 }
1123 # target is a client address in known_clients -> process here
1124 if (not $done) {
1125 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1126 $res = $known_clients_db->select_dbentry($sql);
1127 if (keys(%$res) > 0) {
1128 $done = 1;
1129 my $hostname = $res->{1}->{'hostname'};
1130 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1131 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1132 if ($source eq "GOSA") {
1133 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1134 }
1135 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1137 } else {
1138 $not_found_in_known_clients_db = 1;
1139 }
1140 }
1142 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1143 if (not $done) {
1144 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1145 my $gosa_at;
1146 my $gosa_session_id;
1147 if (($target eq $local_address) && (defined $forward_to_gosa)){
1148 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1149 if ($gosa_at ne $local_address) {
1150 $done = 1;
1151 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1152 }
1153 }
1154 }
1156 # if message should be processed here -> add message to incoming_db
1157 if ($done) {
1158 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1159 # so gosa-si-server knows how to process this kind of messages
1160 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1161 $module = "GosaPackages";
1162 }
1164 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1165 primkey=>[],
1166 headertag=>$header,
1167 targettag=>$target,
1168 xmlmessage=>&encode_base64($msg),
1169 timestamp=>&get_time,
1170 module=>$module,
1171 sessionid=>$session_id,
1172 } );
1174 }
1176 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1177 if (not $done) {
1178 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1179 my $gosa_at;
1180 my $gosa_session_id;
1181 if (($target eq $local_address) && (defined $forward_to_gosa)){
1182 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1183 if ($gosa_at eq $local_address) {
1184 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1185 if( defined $session_reference ) {
1186 $heap = $session_reference->get_heap();
1187 }
1188 if(exists $heap->{'client'}) {
1189 $msg = &encrypt_msg($msg, $GosaPackages_key);
1190 $heap->{'client'}->put($msg);
1191 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1192 }
1193 $done = 1;
1194 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1195 }
1196 }
1198 }
1200 # target is a client address in foreign_clients -> forward to registration server
1201 if (not $done) {
1202 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1203 $res = $foreign_clients_db->select_dbentry($sql);
1204 if (keys(%$res) > 0) {
1205 my $hostname = $res->{1}->{'hostname'};
1206 my ($host_ip, $host_port) = split(/:/, $hostname);
1207 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1208 my $regserver = $res->{1}->{'regserver'};
1209 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1210 my $res = $known_server_db->select_dbentry($sql);
1211 if (keys(%$res) > 0) {
1212 my $regserver_key = $res->{1}->{'hostkey'};
1213 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1214 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1215 if ($source eq "GOSA") {
1216 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1217 }
1218 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1219 }
1220 $done = 1;
1221 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1222 } else {
1223 $not_found_in_foreign_clients_db = 1;
1224 }
1225 }
1227 # target is a server address -> forward to server
1228 if (not $done) {
1229 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1230 $res = $known_server_db->select_dbentry($sql);
1231 if (keys(%$res) > 0) {
1232 my $hostkey = $res->{1}->{'hostkey'};
1234 if ($source eq "GOSA") {
1235 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1236 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1238 }
1240 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1241 $done = 1;
1242 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1243 } else {
1244 $not_found_in_known_server_db = 1;
1245 }
1246 }
1249 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1250 if ( $not_found_in_foreign_clients_db
1251 && $not_found_in_known_server_db
1252 && $not_found_in_known_clients_db) {
1253 &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);
1254 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1255 primkey=>[],
1256 headertag=>$header,
1257 targettag=>$target,
1258 xmlmessage=>&encode_base64($msg),
1259 timestamp=>&get_time,
1260 module=>$module,
1261 sessionid=>$session_id,
1262 } );
1263 $done = 1;
1264 }
1267 if (not $done) {
1268 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1269 if ($source eq "GOSA") {
1270 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1271 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1273 my $session_reference = $kernel->ID_id_to_session($session_id);
1274 if( defined $session_reference ) {
1275 $heap = $session_reference->get_heap();
1276 }
1277 if(exists $heap->{'client'}) {
1278 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1279 $heap->{'client'}->put($error_msg);
1280 }
1281 }
1282 }
1284 }
1286 return;
1287 }
1290 sub next_task {
1291 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1292 my $running_task = POE::Wheel::Run->new(
1293 Program => sub { process_task($session, $heap, $task) },
1294 StdioFilter => POE::Filter::Reference->new(),
1295 StdoutEvent => "task_result",
1296 StderrEvent => "task_debug",
1297 CloseEvent => "task_done",
1298 );
1299 $heap->{task}->{ $running_task->ID } = $running_task;
1300 }
1302 sub handle_task_result {
1303 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1304 my $client_answer = $result->{'answer'};
1305 if( $client_answer =~ s/session_id=(\d+)$// ) {
1306 my $session_id = $1;
1307 if( defined $session_id ) {
1308 my $session_reference = $kernel->ID_id_to_session($session_id);
1309 if( defined $session_reference ) {
1310 $heap = $session_reference->get_heap();
1311 }
1312 }
1314 if(exists $heap->{'client'}) {
1315 $heap->{'client'}->put($client_answer);
1316 }
1317 }
1318 $kernel->sig(CHLD => "child_reap");
1319 }
1321 sub handle_task_debug {
1322 my $result = $_[ARG0];
1323 print STDERR "$result\n";
1324 }
1326 sub handle_task_done {
1327 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1328 delete $heap->{task}->{$task_id};
1329 }
1331 sub process_task {
1332 no strict "refs";
1333 #CHECK: Not @_[...]?
1334 my ($session, $heap, $task) = @_;
1335 my $error = 0;
1336 my $answer_l;
1337 my ($answer_header, @answer_target_l, $answer_source);
1338 my $client_answer = "";
1340 # prepare all variables needed to process message
1341 #my $msg = $task->{'xmlmessage'};
1342 my $msg = &decode_base64($task->{'xmlmessage'});
1343 my $incoming_id = $task->{'id'};
1344 my $module = $task->{'module'};
1345 my $header = $task->{'headertag'};
1346 my $session_id = $task->{'sessionid'};
1347 my $msg_hash;
1348 eval {
1349 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1350 };
1351 daemon_log("ERROR: XML failure '$@'") if ($@);
1352 my $source = @{$msg_hash->{'source'}}[0];
1354 # set timestamp of incoming client uptodate, so client will not
1355 # be deleted from known_clients because of expiration
1356 my $act_time = &get_time();
1357 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1358 my $res = $known_clients_db->exec_statement($sql);
1360 ######################
1361 # process incoming msg
1362 if( $error == 0) {
1363 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1364 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1365 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1367 if ( 0 < @{$answer_l} ) {
1368 my $answer_str = join("\n", @{$answer_l});
1369 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1370 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1371 }
1372 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1373 } else {
1374 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1375 }
1377 }
1378 if( !$answer_l ) { $error++ };
1380 ########
1381 # answer
1382 if( $error == 0 ) {
1384 foreach my $answer ( @{$answer_l} ) {
1385 # check outgoing msg to xml validity
1386 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1387 if( not defined $answer_hash ) { next; }
1389 $answer_header = @{$answer_hash->{'header'}}[0];
1390 @answer_target_l = @{$answer_hash->{'target'}};
1391 $answer_source = @{$answer_hash->{'source'}}[0];
1393 # deliver msg to all targets
1394 foreach my $answer_target ( @answer_target_l ) {
1396 # targets of msg are all gosa-si-clients in known_clients_db
1397 if( $answer_target eq "*" ) {
1398 # answer is for all clients
1399 my $sql_statement= "SELECT * FROM known_clients";
1400 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1401 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1402 my $host_name = $hit->{hostname};
1403 my $host_key = $hit->{hostkey};
1404 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1405 &update_jobdb_status_for_send_msgs($answer, $error);
1406 }
1407 }
1409 # targets of msg are all gosa-si-server in known_server_db
1410 elsif( $answer_target eq "KNOWN_SERVER" ) {
1411 # answer is for all server in known_server
1412 my $sql_statement= "SELECT * FROM $known_server_tn";
1413 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1414 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1415 my $host_name = $hit->{hostname};
1416 my $host_key = $hit->{hostkey};
1417 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1418 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1419 &update_jobdb_status_for_send_msgs($answer, $error);
1420 }
1421 }
1423 # target of msg is GOsa
1424 elsif( $answer_target eq "GOSA" ) {
1425 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1426 my $add_on = "";
1427 if( defined $session_id ) {
1428 $add_on = ".session_id=$session_id";
1429 }
1430 # answer is for GOSA and has to returned to connected client
1431 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1432 $client_answer = $gosa_answer.$add_on;
1433 }
1435 # target of msg is job queue at this host
1436 elsif( $answer_target eq "JOBDB") {
1437 $answer =~ /<header>(\S+)<\/header>/;
1438 my $header;
1439 if( defined $1 ) { $header = $1; }
1440 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1441 &update_jobdb_status_for_send_msgs($answer, $error);
1442 }
1444 # Target of msg is a mac address
1445 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 ) {
1446 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1447 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1448 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1449 my $found_ip_flag = 0;
1450 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1451 my $host_name = $hit->{hostname};
1452 my $host_key = $hit->{hostkey};
1453 $answer =~ s/$answer_target/$host_name/g;
1454 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1455 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1456 &update_jobdb_status_for_send_msgs($answer, $error);
1457 $found_ip_flag++ ;
1458 }
1459 if ($found_ip_flag == 0) {
1460 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1461 my $res = $foreign_clients_db->select_dbentry($sql);
1462 while( my ($hit_num, $hit) = each %{ $res } ) {
1463 my $host_name = $hit->{hostname};
1464 my $reg_server = $hit->{regserver};
1465 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1467 # Fetch key for reg_server
1468 my $reg_server_key;
1469 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1470 my $res = $known_server_db->select_dbentry($sql);
1471 if (exists $res->{1}) {
1472 $reg_server_key = $res->{1}->{'hostkey'};
1473 } else {
1474 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1475 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1476 $reg_server_key = undef;
1477 }
1479 # Send answer to server where client is registered
1480 if (defined $reg_server_key) {
1481 $answer =~ s/$answer_target/$host_name/g;
1482 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1483 &update_jobdb_status_for_send_msgs($answer, $error);
1484 $found_ip_flag++ ;
1485 }
1486 }
1487 }
1488 if( $found_ip_flag == 0) {
1489 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1490 }
1492 # Answer is for one specific host
1493 } else {
1494 # get encrypt_key
1495 my $encrypt_key = &get_encrypt_key($answer_target);
1496 if( not defined $encrypt_key ) {
1497 # unknown target
1498 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1499 next;
1500 }
1501 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1502 &update_jobdb_status_for_send_msgs($answer, $error);
1503 }
1504 }
1505 }
1506 }
1508 my $filter = POE::Filter::Reference->new();
1509 my %result = (
1510 status => "seems ok to me",
1511 answer => $client_answer,
1512 );
1514 my $output = $filter->put( [ \%result ] );
1515 print @$output;
1518 }
1520 sub session_start {
1521 my ($kernel) = $_[KERNEL];
1522 $global_kernel = $kernel;
1523 $kernel->yield('register_at_foreign_servers');
1524 $kernel->yield('create_fai_server_db', $fai_server_tn );
1525 $kernel->yield('create_fai_release_db', $fai_release_tn );
1526 $kernel->yield('watch_for_next_tasks');
1527 $kernel->sig(USR1 => "sig_handler");
1528 $kernel->sig(USR2 => "recreate_packages_db");
1529 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1530 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1531 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1532 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1533 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1534 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1535 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1537 # Start opsi check
1538 if ($opsi_enabled eq "true") {
1539 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1540 }
1542 }
1545 sub watch_for_done_jobs {
1546 #CHECK: $heap for what?
1547 my ($kernel,$heap) = @_[KERNEL, HEAP];
1549 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1550 my $res = $job_db->select_dbentry( $sql_statement );
1552 while( my ($id, $hit) = each %{$res} ) {
1553 my $jobdb_id = $hit->{id};
1554 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1555 my $res = $job_db->del_dbentry($sql_statement);
1556 }
1558 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1559 }
1562 sub watch_for_opsi_jobs {
1563 my ($kernel) = $_[KERNEL];
1565 # 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
1566 # opsi install job is to parse the xml message. There is still the correct header.
1567 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1568 my $res = $job_db->select_dbentry( $sql_statement );
1570 # Ask OPSI for an update of the running jobs
1571 while (my ($id, $hit) = each %$res ) {
1572 # Determine current parameters of the job
1573 my $hostId = $hit->{'plainname'};
1574 my $macaddress = $hit->{'macaddress'};
1575 my $progress = $hit->{'progress'};
1577 my $result= {};
1579 # For hosts, only return the products that are or get installed
1580 my $callobj;
1581 $callobj = {
1582 method => 'getProductStates_hash',
1583 params => [ $hostId ],
1584 id => 1,
1585 };
1587 my $hres = $opsi_client->call($opsi_url, $callobj);
1588 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1589 if (not &check_opsi_res($hres)) {
1590 my $htmp= $hres->result->{$hostId};
1592 # Check state != not_installed or action == setup -> load and add
1593 my $products= 0;
1594 my $installed= 0;
1595 my $installing = 0;
1596 my $error= 0;
1597 my @installed_list;
1598 my @error_list;
1599 my $act_status = "none";
1600 foreach my $product (@{$htmp}){
1602 if ($product->{'installationStatus'} ne "not_installed" or
1603 $product->{'actionRequest'} eq "setup"){
1605 # Increase number of products for this host
1606 $products++;
1608 if ($product->{'installationStatus'} eq "failed"){
1609 $result->{$product->{'productId'}}= "error";
1610 unshift(@error_list, $product->{'productId'});
1611 $error++;
1612 }
1613 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1614 $result->{$product->{'productId'}}= "installed";
1615 unshift(@installed_list, $product->{'productId'});
1616 $installed++;
1617 }
1618 if ($product->{'installationStatus'} eq "installing"){
1619 $result->{$product->{'productId'}}= "installing";
1620 $installing++;
1621 $act_status = "installing - ".$product->{'productId'};
1622 }
1623 }
1624 }
1626 # Estimate "rough" progress, avoid division by zero
1627 if ($products == 0) {
1628 $result->{'progress'}= 0;
1629 } else {
1630 $result->{'progress'}= int($installed * 100 / $products);
1631 }
1633 # Set updates in job queue
1634 if ((not $error) && (not $installing) && ($installed)) {
1635 $act_status = "installed - ".join(", ", @installed_list);
1636 }
1637 if ($error) {
1638 $act_status = "error - ".join(", ", @error_list);
1639 }
1640 if ($progress ne $result->{'progress'} ) {
1641 # Updating progress and result
1642 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1643 my $update_res = $job_db->update_dbentry($update_statement);
1644 }
1645 if ($progress eq 100) {
1646 # Updateing status
1647 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1648 if ($error) {
1649 $done_statement .= "status='error'";
1650 } else {
1651 $done_statement .= "status='done'";
1652 }
1653 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1654 my $done_res = $job_db->update_dbentry($done_statement);
1655 }
1658 }
1659 }
1661 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1662 }
1665 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1666 sub watch_for_modified_jobs {
1667 my ($kernel,$heap) = @_[KERNEL, HEAP];
1669 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))";
1670 my $res = $job_db->select_dbentry( $sql_statement );
1672 # if db contains no jobs which should be update, do nothing
1673 if (keys %$res != 0) {
1675 if ($job_synchronization eq "true") {
1676 # make out of the db result a gosa-si message
1677 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1679 # update all other SI-server
1680 &inform_all_other_si_server($update_msg);
1681 }
1683 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1684 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1685 $res = $job_db->update_dbentry($sql_statement);
1686 }
1688 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1689 }
1692 sub watch_for_new_jobs {
1693 if($watch_for_new_jobs_in_progress == 0) {
1694 $watch_for_new_jobs_in_progress = 1;
1695 my ($kernel,$heap) = @_[KERNEL, HEAP];
1697 # check gosa job quaeue for jobs with executable timestamp
1698 my $timestamp = &get_time();
1699 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1700 my $res = $job_db->exec_statement( $sql_statement );
1702 # Merge all new jobs that would do the same actions
1703 my @drops;
1704 my $hits;
1705 foreach my $hit (reverse @{$res} ) {
1706 my $macaddress= lc @{$hit}[8];
1707 my $headertag= @{$hit}[5];
1708 if(
1709 defined($hits->{$macaddress}) &&
1710 defined($hits->{$macaddress}->{$headertag}) &&
1711 defined($hits->{$macaddress}->{$headertag}[0])
1712 ) {
1713 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1714 }
1715 $hits->{$macaddress}->{$headertag}= $hit;
1716 }
1718 # Delete new jobs with a matching job in state 'processing'
1719 foreach my $macaddress (keys %{$hits}) {
1720 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1721 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1722 if(defined($jobdb_id)) {
1723 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1724 my $res = $job_db->exec_statement( $sql_statement );
1725 foreach my $hit (@{$res}) {
1726 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1727 }
1728 } else {
1729 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1730 }
1731 }
1732 }
1734 # Commit deletion
1735 $job_db->exec_statementlist(\@drops);
1737 # Look for new jobs that could be executed
1738 foreach my $macaddress (keys %{$hits}) {
1740 # Look if there is an executing job
1741 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1742 my $res = $job_db->exec_statement( $sql_statement );
1744 # Skip new jobs for host if there is a processing job
1745 if(defined($res) and defined @{$res}[0]) {
1746 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1747 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1748 if(@{$row}[5] eq 'trigger_action_reinstall') {
1749 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1750 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1751 if(defined($res_2) and defined @{$res_2}[0]) {
1752 # Set status from goto-activation to 'waiting' and update timestamp
1753 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1754 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1755 }
1756 }
1757 next;
1758 }
1760 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1761 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1762 if(defined($jobdb_id)) {
1763 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1765 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1766 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1767 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1769 # expect macaddress is unique!!!!!!
1770 my $target = $res_hash->{1}->{hostname};
1772 # change header
1773 $job_msg =~ s/<header>job_/<header>gosa_/;
1775 # add sqlite_id
1776 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1778 $job_msg =~ /<header>(\S+)<\/header>/;
1779 my $header = $1 ;
1780 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1782 # update status in job queue to 'processing'
1783 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1784 my $res = $job_db->update_dbentry($sql_statement);
1785 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1787 # We don't want parallel processing
1788 last;
1789 }
1790 }
1791 }
1793 $watch_for_new_jobs_in_progress = 0;
1794 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1795 }
1796 }
1799 sub watch_for_new_messages {
1800 my ($kernel,$heap) = @_[KERNEL, HEAP];
1801 my @coll_user_msg; # collection list of outgoing messages
1803 # check messaging_db for new incoming messages with executable timestamp
1804 my $timestamp = &get_time();
1805 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1806 my $res = $messaging_db->exec_statement( $sql_statement );
1807 foreach my $hit (@{$res}) {
1809 # create outgoing messages
1810 my $message_to = @{$hit}[3];
1811 # translate message_to to plain login name
1812 my @message_to_l = split(/,/, $message_to);
1813 my %receiver_h;
1814 foreach my $receiver (@message_to_l) {
1815 if ($receiver =~ /^u_([\s\S]*)$/) {
1816 $receiver_h{$1} = 0;
1817 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1818 my $group_name = $1;
1819 # fetch all group members from ldap and add them to receiver hash
1820 my $ldap_handle = &get_ldap_handle();
1821 if (defined $ldap_handle) {
1822 my $mesg = $ldap_handle->search(
1823 base => $ldap_base,
1824 scope => 'sub',
1825 attrs => ['memberUid'],
1826 filter => "cn=$group_name",
1827 );
1828 if ($mesg->count) {
1829 my @entries = $mesg->entries;
1830 foreach my $entry (@entries) {
1831 my @receivers= $entry->get_value("memberUid");
1832 foreach my $receiver (@receivers) {
1833 $receiver_h{$1} = 0;
1834 }
1835 }
1836 }
1837 # translating errors ?
1838 if ($mesg->code) {
1839 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1840 }
1841 # ldap handle error ?
1842 } else {
1843 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1844 }
1845 } else {
1846 my $sbjct = &encode_base64(@{$hit}[1]);
1847 my $msg = &encode_base64(@{$hit}[7]);
1848 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1849 }
1850 }
1851 my @receiver_l = keys(%receiver_h);
1853 my $message_id = @{$hit}[0];
1855 #add each outgoing msg to messaging_db
1856 my $receiver;
1857 foreach $receiver (@receiver_l) {
1858 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1859 "VALUES ('".
1860 $message_id."', '". # id
1861 @{$hit}[1]."', '". # subject
1862 @{$hit}[2]."', '". # message_from
1863 $receiver."', '". # message_to
1864 "none"."', '". # flag
1865 "out"."', '". # direction
1866 @{$hit}[6]."', '". # delivery_time
1867 @{$hit}[7]."', '". # message
1868 $timestamp."'". # timestamp
1869 ")";
1870 &daemon_log("M DEBUG: $sql_statement", 1);
1871 my $res = $messaging_db->exec_statement($sql_statement);
1872 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1873 }
1875 # set incoming message to flag d=deliverd
1876 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1877 &daemon_log("M DEBUG: $sql_statement", 7);
1878 $res = $messaging_db->update_dbentry($sql_statement);
1879 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1880 }
1882 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1883 return;
1884 }
1886 sub watch_for_delivery_messages {
1887 my ($kernel, $heap) = @_[KERNEL, HEAP];
1889 # select outgoing messages
1890 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1891 #&daemon_log("0 DEBUG: $sql", 7);
1892 my $res = $messaging_db->exec_statement( $sql_statement );
1894 # build out msg for each usr
1895 foreach my $hit (@{$res}) {
1896 my $receiver = @{$hit}[3];
1897 my $msg_id = @{$hit}[0];
1898 my $subject = @{$hit}[1];
1899 my $message = @{$hit}[7];
1901 # resolve usr -> host where usr is logged in
1902 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1903 #&daemon_log("0 DEBUG: $sql", 7);
1904 my $res = $login_users_db->exec_statement($sql);
1906 # reciver is logged in nowhere
1907 if (not ref(@$res[0]) eq "ARRAY") { next; }
1909 my $send_succeed = 0;
1910 foreach my $hit (@$res) {
1911 my $receiver_host = @$hit[0];
1912 my $delivered2host = 0;
1913 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1915 # Looking for host in know_clients_db
1916 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1917 my $res = $known_clients_db->exec_statement($sql);
1919 # Host is known in known_clients_db
1920 if (ref(@$res[0]) eq "ARRAY") {
1921 my $receiver_key = @{@{$res}[0]}[2];
1922 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1923 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1924 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1925 if ($error == 0 ) {
1926 $send_succeed++ ;
1927 $delivered2host++ ;
1928 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1929 } else {
1930 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1931 }
1932 }
1934 # Message already send, do not need to do anything more, otherwise ...
1935 if ($delivered2host) { next;}
1937 # ...looking for host in foreign_clients_db
1938 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1939 $res = $foreign_clients_db->exec_statement($sql);
1941 # Host is known in foreign_clients_db
1942 if (ref(@$res[0]) eq "ARRAY") {
1943 my $registration_server = @{@{$res}[0]}[2];
1945 # Fetch encryption key for registration server
1946 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1947 my $res = $known_server_db->exec_statement($sql);
1948 if (ref(@$res[0]) eq "ARRAY") {
1949 my $registration_server_key = @{@{$res}[0]}[3];
1950 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1951 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1952 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1953 if ($error == 0 ) {
1954 $send_succeed++ ;
1955 $delivered2host++ ;
1956 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
1957 } else {
1958 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
1959 }
1961 } else {
1962 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1963 "registrated at server '$registration_server', ".
1964 "but no data available in known_server_db ", 1);
1965 }
1966 }
1968 if (not $delivered2host) {
1969 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1970 }
1971 }
1973 if ($send_succeed) {
1974 # set outgoing msg at db to deliverd
1975 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1976 my $res = $messaging_db->exec_statement($sql);
1977 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1978 } else {
1979 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
1980 }
1981 }
1983 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1984 return;
1985 }
1988 sub watch_for_done_messages {
1989 my ($kernel,$heap) = @_[KERNEL, HEAP];
1991 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1992 #&daemon_log("0 DEBUG: $sql", 7);
1993 my $res = $messaging_db->exec_statement($sql);
1995 foreach my $hit (@{$res}) {
1996 my $msg_id = @{$hit}[0];
1998 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1999 #&daemon_log("0 DEBUG: $sql", 7);
2000 my $res = $messaging_db->exec_statement($sql);
2002 # not all usr msgs have been seen till now
2003 if ( ref(@$res[0]) eq "ARRAY") { next; }
2005 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2006 #&daemon_log("0 DEBUG: $sql", 7);
2007 $res = $messaging_db->exec_statement($sql);
2009 }
2011 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2012 return;
2013 }
2016 sub watch_for_old_known_clients {
2017 my ($kernel,$heap) = @_[KERNEL, HEAP];
2019 my $sql_statement = "SELECT * FROM $known_clients_tn";
2020 my $res = $known_clients_db->select_dbentry( $sql_statement );
2022 my $act_time = int(&get_time());
2024 while ( my ($hit_num, $hit) = each %$res) {
2025 my $expired_timestamp = int($hit->{'timestamp'});
2026 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2027 my $dt = DateTime->new( year => $1,
2028 month => $2,
2029 day => $3,
2030 hour => $4,
2031 minute => $5,
2032 second => $6,
2033 );
2035 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2036 $expired_timestamp = $dt->ymd('').$dt->hms('');
2037 if ($act_time > $expired_timestamp) {
2038 my $hostname = $hit->{'hostname'};
2039 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2040 my $del_res = $known_clients_db->exec_statement($del_sql);
2042 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2043 }
2045 }
2047 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2048 }
2051 sub watch_for_next_tasks {
2052 my ($kernel,$heap) = @_[KERNEL, HEAP];
2054 my $sql = "SELECT * FROM $incoming_tn";
2055 my $res = $incoming_db->select_dbentry($sql);
2057 while ( my ($hit_num, $hit) = each %$res) {
2058 my $headertag = $hit->{'headertag'};
2059 if ($headertag =~ /^answer_(\d+)/) {
2060 # do not start processing, this message is for a still running POE::Wheel
2061 next;
2062 }
2063 my $message_id = $hit->{'id'};
2064 my $session_id = $hit->{'sessionid'};
2065 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2066 $kernel->yield('next_task', $hit);
2068 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2069 my $res = $incoming_db->exec_statement($sql);
2070 }
2072 $kernel->delay_set('watch_for_next_tasks', 1);
2073 }
2076 sub get_ldap_handle {
2077 my ($session_id) = @_;
2078 my $heap;
2079 my $ldap_handle;
2081 if (not defined $session_id ) { $session_id = 0 };
2082 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2084 if ($session_id == 0) {
2085 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2086 $ldap_handle = Net::LDAP->new( $ldap_uri );
2087 if (defined $ldap_handle) {
2088 $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!");
2089 } else {
2090 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2091 }
2093 } else {
2094 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2095 if( defined $session_reference ) {
2096 $heap = $session_reference->get_heap();
2097 }
2099 if (not defined $heap) {
2100 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2101 return;
2102 }
2104 # TODO: This "if" is nonsense, because it doesn't prove that the
2105 # used handle is still valid - or if we've to reconnect...
2106 #if (not exists $heap->{ldap_handle}) {
2107 $ldap_handle = Net::LDAP->new( $ldap_uri );
2108 $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!");
2109 $heap->{ldap_handle} = $ldap_handle;
2110 #}
2111 }
2112 return $ldap_handle;
2113 }
2116 sub change_fai_state {
2117 my ($st, $targets, $session_id) = @_;
2118 $session_id = 0 if not defined $session_id;
2119 # Set FAI state to localboot
2120 my %mapActions= (
2121 reboot => '',
2122 update => 'softupdate',
2123 localboot => 'localboot',
2124 reinstall => 'install',
2125 rescan => '',
2126 wake => '',
2127 memcheck => 'memcheck',
2128 sysinfo => 'sysinfo',
2129 install => 'install',
2130 );
2132 # Return if this is unknown
2133 if (!exists $mapActions{ $st }){
2134 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2135 return;
2136 }
2138 my $state= $mapActions{ $st };
2140 my $ldap_handle = &get_ldap_handle($session_id);
2141 if( defined($ldap_handle) ) {
2143 # Build search filter for hosts
2144 my $search= "(&(objectClass=GOhard)";
2145 foreach (@{$targets}){
2146 $search.= "(macAddress=$_)";
2147 }
2148 $search.= ")";
2150 # If there's any host inside of the search string, procress them
2151 if (!($search =~ /macAddress/)){
2152 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2153 return;
2154 }
2156 # Perform search for Unit Tag
2157 my $mesg = $ldap_handle->search(
2158 base => $ldap_base,
2159 scope => 'sub',
2160 attrs => ['dn', 'FAIstate', 'objectClass'],
2161 filter => "$search"
2162 );
2164 if ($mesg->count) {
2165 my @entries = $mesg->entries;
2166 if (0 == @entries) {
2167 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2168 }
2170 foreach my $entry (@entries) {
2171 # Only modify entry if it is not set to '$state'
2172 if ($entry->get_value("FAIstate") ne "$state"){
2173 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2174 my $result;
2175 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2176 if (exists $tmp{'FAIobject'}){
2177 if ($state eq ''){
2178 $result= $ldap_handle->modify($entry->dn, changes => [
2179 delete => [ FAIstate => [] ] ]);
2180 } else {
2181 $result= $ldap_handle->modify($entry->dn, changes => [
2182 replace => [ FAIstate => $state ] ]);
2183 }
2184 } elsif ($state ne ''){
2185 $result= $ldap_handle->modify($entry->dn, changes => [
2186 add => [ objectClass => 'FAIobject' ],
2187 add => [ FAIstate => $state ] ]);
2188 }
2190 # Errors?
2191 if ($result->code){
2192 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2193 }
2194 } else {
2195 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2196 }
2197 }
2198 } else {
2199 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2200 }
2202 # if no ldap handle defined
2203 } else {
2204 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2205 }
2207 return;
2208 }
2211 sub change_goto_state {
2212 my ($st, $targets, $session_id) = @_;
2213 $session_id = 0 if not defined $session_id;
2215 # Switch on or off?
2216 my $state= $st eq 'active' ? 'active': 'locked';
2218 my $ldap_handle = &get_ldap_handle($session_id);
2219 if( defined($ldap_handle) ) {
2221 # Build search filter for hosts
2222 my $search= "(&(objectClass=GOhard)";
2223 foreach (@{$targets}){
2224 $search.= "(macAddress=$_)";
2225 }
2226 $search.= ")";
2228 # If there's any host inside of the search string, procress them
2229 if (!($search =~ /macAddress/)){
2230 return;
2231 }
2233 # Perform search for Unit Tag
2234 my $mesg = $ldap_handle->search(
2235 base => $ldap_base,
2236 scope => 'sub',
2237 attrs => ['dn', 'gotoMode'],
2238 filter => "$search"
2239 );
2241 if ($mesg->count) {
2242 my @entries = $mesg->entries;
2243 foreach my $entry (@entries) {
2245 # Only modify entry if it is not set to '$state'
2246 if ($entry->get_value("gotoMode") ne $state){
2248 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2249 my $result;
2250 $result= $ldap_handle->modify($entry->dn, changes => [
2251 replace => [ gotoMode => $state ] ]);
2253 # Errors?
2254 if ($result->code){
2255 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2256 }
2258 }
2259 }
2260 } else {
2261 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2262 }
2264 }
2265 }
2268 sub run_recreate_packages_db {
2269 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2270 my $session_id = $session->ID;
2271 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2272 $kernel->yield('create_fai_release_db', $fai_release_tn);
2273 $kernel->yield('create_fai_server_db', $fai_server_tn);
2274 return;
2275 }
2278 sub run_create_fai_server_db {
2279 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2280 my $session_id = $session->ID;
2281 my $task = POE::Wheel::Run->new(
2282 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2283 StdoutEvent => "session_run_result",
2284 StderrEvent => "session_run_debug",
2285 CloseEvent => "session_run_done",
2286 );
2288 $heap->{task}->{ $task->ID } = $task;
2289 return;
2290 }
2293 sub create_fai_server_db {
2294 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2295 my $result;
2297 if (not defined $session_id) { $session_id = 0; }
2298 my $ldap_handle = &get_ldap_handle();
2299 if(defined($ldap_handle)) {
2300 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2301 my $mesg= $ldap_handle->search(
2302 base => $ldap_base,
2303 scope => 'sub',
2304 attrs => ['FAIrepository', 'gosaUnitTag'],
2305 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2306 );
2307 if($mesg->{'resultCode'} == 0 &&
2308 $mesg->count != 0) {
2309 foreach my $entry (@{$mesg->{entries}}) {
2310 if($entry->exists('FAIrepository')) {
2311 # Add an entry for each Repository configured for server
2312 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2313 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2314 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2315 $result= $fai_server_db->add_dbentry( {
2316 table => $table_name,
2317 primkey => ['server', 'fai_release', 'tag'],
2318 server => $tmp_url,
2319 fai_release => $tmp_release,
2320 sections => $tmp_sections,
2321 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2322 } );
2323 }
2324 }
2325 }
2326 }
2327 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2329 # TODO: Find a way to post the 'create_packages_list_db' event
2330 if(not defined($dont_create_packages_list)) {
2331 &create_packages_list_db(undef, undef, $session_id);
2332 }
2333 }
2335 $ldap_handle->disconnect;
2336 return $result;
2337 }
2340 sub run_create_fai_release_db {
2341 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2342 my $session_id = $session->ID;
2343 my $task = POE::Wheel::Run->new(
2344 Program => sub { &create_fai_release_db($table_name, $session_id) },
2345 StdoutEvent => "session_run_result",
2346 StderrEvent => "session_run_debug",
2347 CloseEvent => "session_run_done",
2348 );
2350 $heap->{task}->{ $task->ID } = $task;
2351 return;
2352 }
2355 sub create_fai_release_db {
2356 my ($table_name, $session_id) = @_;
2357 my $result;
2359 # used for logging
2360 if (not defined $session_id) { $session_id = 0; }
2362 my $ldap_handle = &get_ldap_handle();
2363 if(defined($ldap_handle)) {
2364 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2365 my $mesg= $ldap_handle->search(
2366 base => $ldap_base,
2367 scope => 'sub',
2368 attrs => [],
2369 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2370 );
2371 if($mesg->{'resultCode'} == 0 &&
2372 $mesg->count != 0) {
2373 # Walk through all possible FAI container ou's
2374 my @sql_list;
2375 my $timestamp= &get_time();
2376 foreach my $ou (@{$mesg->{entries}}) {
2377 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2378 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2379 my @tmp_array=get_fai_release_entries($tmp_classes);
2380 if(@tmp_array) {
2381 foreach my $entry (@tmp_array) {
2382 if(defined($entry) && ref($entry) eq 'HASH') {
2383 my $sql=
2384 "INSERT INTO $table_name "
2385 ."(timestamp, fai_release, class, type, state) VALUES ("
2386 .$timestamp.","
2387 ."'".$entry->{'release'}."',"
2388 ."'".$entry->{'class'}."',"
2389 ."'".$entry->{'type'}."',"
2390 ."'".$entry->{'state'}."')";
2391 push @sql_list, $sql;
2392 }
2393 }
2394 }
2395 }
2396 }
2398 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2399 if(@sql_list) {
2400 unshift @sql_list, "DELETE FROM $table_name";
2401 $fai_release_db->exec_statementlist(\@sql_list);
2402 }
2403 daemon_log("$session_id DEBUG: Done with inserting",7);
2404 }
2405 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2406 }
2407 $ldap_handle->disconnect;
2408 return $result;
2409 }
2411 sub get_fai_types {
2412 my $tmp_classes = shift || return undef;
2413 my @result;
2415 foreach my $type(keys %{$tmp_classes}) {
2416 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2417 my $entry = {
2418 type => $type,
2419 state => $tmp_classes->{$type}[0],
2420 };
2421 push @result, $entry;
2422 }
2423 }
2425 return @result;
2426 }
2428 sub get_fai_state {
2429 my $result = "";
2430 my $tmp_classes = shift || return $result;
2432 foreach my $type(keys %{$tmp_classes}) {
2433 if(defined($tmp_classes->{$type}[0])) {
2434 $result = $tmp_classes->{$type}[0];
2436 # State is equal for all types in class
2437 last;
2438 }
2439 }
2441 return $result;
2442 }
2444 sub resolve_fai_classes {
2445 my ($fai_base, $ldap_handle, $session_id) = @_;
2446 if (not defined $session_id) { $session_id = 0; }
2447 my $result;
2448 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2449 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2450 my $fai_classes;
2452 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2453 my $mesg= $ldap_handle->search(
2454 base => $fai_base,
2455 scope => 'sub',
2456 attrs => ['cn','objectClass','FAIstate'],
2457 filter => $fai_filter,
2458 );
2459 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2461 if($mesg->{'resultCode'} == 0 &&
2462 $mesg->count != 0) {
2463 foreach my $entry (@{$mesg->{entries}}) {
2464 if($entry->exists('cn')) {
2465 my $tmp_dn= $entry->dn();
2467 # Skip classname and ou dn parts for class
2468 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2470 # Skip classes without releases
2471 if((!defined($tmp_release)) || length($tmp_release)==0) {
2472 next;
2473 }
2475 my $tmp_cn= $entry->get_value('cn');
2476 my $tmp_state= $entry->get_value('FAIstate');
2478 my $tmp_type;
2479 # Get FAI type
2480 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2481 if(grep $_ eq $oclass, @possible_fai_classes) {
2482 $tmp_type= $oclass;
2483 last;
2484 }
2485 }
2487 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2488 # A Subrelease
2489 my @sub_releases = split(/,/, $tmp_release);
2491 # Walk through subreleases and build hash tree
2492 my $hash;
2493 while(my $tmp_sub_release = pop @sub_releases) {
2494 $hash .= "\{'$tmp_sub_release'\}->";
2495 }
2496 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2497 } else {
2498 # A branch, no subrelease
2499 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2500 }
2501 } elsif (!$entry->exists('cn')) {
2502 my $tmp_dn= $entry->dn();
2503 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2505 # Skip classes without releases
2506 if((!defined($tmp_release)) || length($tmp_release)==0) {
2507 next;
2508 }
2510 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2511 # A Subrelease
2512 my @sub_releases= split(/,/, $tmp_release);
2514 # Walk through subreleases and build hash tree
2515 my $hash;
2516 while(my $tmp_sub_release = pop @sub_releases) {
2517 $hash .= "\{'$tmp_sub_release'\}->";
2518 }
2519 # Remove the last two characters
2520 chop($hash);
2521 chop($hash);
2523 eval('$fai_classes->'.$hash.'= {}');
2524 } else {
2525 # A branch, no subrelease
2526 if(!exists($fai_classes->{$tmp_release})) {
2527 $fai_classes->{$tmp_release} = {};
2528 }
2529 }
2530 }
2531 }
2533 # The hash is complete, now we can honor the copy-on-write based missing entries
2534 foreach my $release (keys %$fai_classes) {
2535 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2536 }
2537 }
2538 return $result;
2539 }
2541 sub apply_fai_inheritance {
2542 my $fai_classes = shift || return {};
2543 my $tmp_classes;
2545 # Get the classes from the branch
2546 foreach my $class (keys %{$fai_classes}) {
2547 # Skip subreleases
2548 if($class =~ /^ou=.*$/) {
2549 next;
2550 } else {
2551 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2552 }
2553 }
2555 # Apply to each subrelease
2556 foreach my $subrelease (keys %{$fai_classes}) {
2557 if($subrelease =~ /ou=/) {
2558 foreach my $tmp_class (keys %{$tmp_classes}) {
2559 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2560 $fai_classes->{$subrelease}->{$tmp_class} =
2561 deep_copy($tmp_classes->{$tmp_class});
2562 } else {
2563 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2564 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2565 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2566 deep_copy($tmp_classes->{$tmp_class}->{$type});
2567 }
2568 }
2569 }
2570 }
2571 }
2572 }
2574 # Find subreleases in deeper levels
2575 foreach my $subrelease (keys %{$fai_classes}) {
2576 if($subrelease =~ /ou=/) {
2577 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2578 if($subsubrelease =~ /ou=/) {
2579 apply_fai_inheritance($fai_classes->{$subrelease});
2580 }
2581 }
2582 }
2583 }
2585 return $fai_classes;
2586 }
2588 sub get_fai_release_entries {
2589 my $tmp_classes = shift || return;
2590 my $parent = shift || "";
2591 my @result = shift || ();
2593 foreach my $entry (keys %{$tmp_classes}) {
2594 if(defined($entry)) {
2595 if($entry =~ /^ou=.*$/) {
2596 my $release_name = $entry;
2597 $release_name =~ s/ou=//g;
2598 if(length($parent)>0) {
2599 $release_name = $parent."/".$release_name;
2600 }
2601 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2602 foreach my $bufentry(@bufentries) {
2603 push @result, $bufentry;
2604 }
2605 } else {
2606 my @types = get_fai_types($tmp_classes->{$entry});
2607 foreach my $type (@types) {
2608 push @result,
2609 {
2610 'class' => $entry,
2611 'type' => $type->{'type'},
2612 'release' => $parent,
2613 'state' => $type->{'state'},
2614 };
2615 }
2616 }
2617 }
2618 }
2620 return @result;
2621 }
2623 sub deep_copy {
2624 my $this = shift;
2625 if (not ref $this) {
2626 $this;
2627 } elsif (ref $this eq "ARRAY") {
2628 [map deep_copy($_), @$this];
2629 } elsif (ref $this eq "HASH") {
2630 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2631 } else { die "what type is $_?" }
2632 }
2635 sub session_run_result {
2636 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2637 $kernel->sig(CHLD => "child_reap");
2638 }
2640 sub session_run_debug {
2641 my $result = $_[ARG0];
2642 print STDERR "$result\n";
2643 }
2645 sub session_run_done {
2646 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2647 delete $heap->{task}->{$task_id};
2648 }
2651 sub create_sources_list {
2652 my $session_id = shift;
2653 my $ldap_handle = &main::get_ldap_handle;
2654 my $result="/tmp/gosa_si_tmp_sources_list";
2656 # Remove old file
2657 if(stat($result)) {
2658 unlink($result);
2659 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2660 }
2662 my $fh;
2663 open($fh, ">$result");
2664 if (not defined $fh) {
2665 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2666 return undef;
2667 }
2668 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2669 my $mesg=$ldap_handle->search(
2670 base => $main::ldap_server_dn,
2671 scope => 'base',
2672 attrs => 'FAIrepository',
2673 filter => 'objectClass=FAIrepositoryServer'
2674 );
2675 if($mesg->count) {
2676 foreach my $entry(@{$mesg->{'entries'}}) {
2677 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2678 my ($server, $tag, $release, $sections)= split /\|/, $value;
2679 my $line = "deb $server $release";
2680 $sections =~ s/,/ /g;
2681 $line.= " $sections";
2682 print $fh $line."\n";
2683 }
2684 }
2685 }
2686 } else {
2687 if (defined $main::ldap_server_dn){
2688 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2689 } else {
2690 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2691 }
2692 }
2693 close($fh);
2695 return $result;
2696 }
2699 sub run_create_packages_list_db {
2700 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2701 my $session_id = $session->ID;
2703 my $task = POE::Wheel::Run->new(
2704 Priority => +20,
2705 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2706 StdoutEvent => "session_run_result",
2707 StderrEvent => "session_run_debug",
2708 CloseEvent => "session_run_done",
2709 );
2710 $heap->{task}->{ $task->ID } = $task;
2711 }
2714 sub create_packages_list_db {
2715 my ($ldap_handle, $sources_file, $session_id) = @_;
2717 # it should not be possible to trigger a recreation of packages_list_db
2718 # while packages_list_db is under construction, so set flag packages_list_under_construction
2719 # which is tested befor recreation can be started
2720 if (-r $packages_list_under_construction) {
2721 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2722 return;
2723 } else {
2724 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2725 # set packages_list_under_construction to true
2726 system("touch $packages_list_under_construction");
2727 @packages_list_statements=();
2728 }
2730 if (not defined $session_id) { $session_id = 0; }
2731 if (not defined $ldap_handle) {
2732 $ldap_handle= &get_ldap_handle();
2734 if (not defined $ldap_handle) {
2735 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2736 unlink($packages_list_under_construction);
2737 return;
2738 }
2739 }
2740 if (not defined $sources_file) {
2741 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2742 $sources_file = &create_sources_list($session_id);
2743 }
2745 if (not defined $sources_file) {
2746 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2747 unlink($packages_list_under_construction);
2748 return;
2749 }
2751 my $line;
2753 open(CONFIG, "<$sources_file") or do {
2754 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2755 unlink($packages_list_under_construction);
2756 return;
2757 };
2759 # Read lines
2760 while ($line = <CONFIG>){
2761 # Unify
2762 chop($line);
2763 $line =~ s/^\s+//;
2764 $line =~ s/^\s+/ /;
2766 # Strip comments
2767 $line =~ s/#.*$//g;
2769 # Skip empty lines
2770 if ($line =~ /^\s*$/){
2771 next;
2772 }
2774 # Interpret deb line
2775 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2776 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2777 my $section;
2778 foreach $section (split(' ', $sections)){
2779 &parse_package_info( $baseurl, $dist, $section, $session_id );
2780 }
2781 }
2782 }
2784 close (CONFIG);
2787 if(keys(%repo_dirs)) {
2788 find(\&cleanup_and_extract, keys( %repo_dirs ));
2789 &main::strip_packages_list_statements();
2790 $packages_list_db->exec_statementlist(\@packages_list_statements);
2791 }
2792 unlink($packages_list_under_construction);
2793 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2794 return;
2795 }
2797 # This function should do some intensive task to minimize the db-traffic
2798 sub strip_packages_list_statements {
2799 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2800 my @new_statement_list=();
2801 my $hash;
2802 my $insert_hash;
2803 my $update_hash;
2804 my $delete_hash;
2805 my $local_timestamp=get_time();
2807 foreach my $existing_entry (@existing_entries) {
2808 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2809 }
2811 foreach my $statement (@packages_list_statements) {
2812 if($statement =~ /^INSERT/i) {
2813 # Assign the values from the insert statement
2814 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2815 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2816 if(exists($hash->{$distribution}->{$package}->{$version})) {
2817 # If section or description has changed, update the DB
2818 if(
2819 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2820 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2821 ) {
2822 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2823 }
2824 } else {
2825 # Insert a non-existing entry to db
2826 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2827 }
2828 } elsif ($statement =~ /^UPDATE/i) {
2829 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2830 /^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;
2831 foreach my $distribution (keys %{$hash}) {
2832 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2833 # update the insertion hash to execute only one query per package (insert instead insert+update)
2834 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2835 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2836 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2837 my $section;
2838 my $description;
2839 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2840 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2841 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2842 }
2843 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2844 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2845 }
2846 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2847 }
2848 }
2849 }
2850 }
2851 }
2853 # TODO: Check for orphaned entries
2855 # unroll the insert_hash
2856 foreach my $distribution (keys %{$insert_hash}) {
2857 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2858 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2859 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2860 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2861 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2862 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2863 ."'$local_timestamp')";
2864 }
2865 }
2866 }
2868 # unroll the update hash
2869 foreach my $distribution (keys %{$update_hash}) {
2870 foreach my $package (keys %{$update_hash->{$distribution}}) {
2871 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2872 my $set = "";
2873 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2874 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2875 }
2876 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2877 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2878 }
2879 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2880 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2881 }
2882 if(defined($set) and length($set) > 0) {
2883 $set .= "timestamp = '$local_timestamp'";
2884 } else {
2885 next;
2886 }
2887 push @new_statement_list,
2888 "UPDATE $main::packages_list_tn SET $set WHERE"
2889 ." distribution = '$distribution'"
2890 ." AND package = '$package'"
2891 ." AND version = '$version'";
2892 }
2893 }
2894 }
2896 @packages_list_statements = @new_statement_list;
2897 }
2900 sub parse_package_info {
2901 my ($baseurl, $dist, $section, $session_id)= @_;
2902 my ($package);
2903 if (not defined $session_id) { $session_id = 0; }
2904 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2905 $repo_dirs{ "${repo_path}/pool" } = 1;
2907 foreach $package ("Packages.gz"){
2908 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2909 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2910 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2911 }
2913 }
2916 sub get_package {
2917 my ($url, $dest, $session_id)= @_;
2918 if (not defined $session_id) { $session_id = 0; }
2920 my $tpath = dirname($dest);
2921 -d "$tpath" || mkpath "$tpath";
2923 # This is ugly, but I've no time to take a look at "how it works in perl"
2924 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2925 system("gunzip -cd '$dest' > '$dest.in'");
2926 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2927 unlink($dest);
2928 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2929 } else {
2930 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2931 }
2932 return 0;
2933 }
2936 sub parse_package {
2937 my ($path, $dist, $srv_path, $session_id)= @_;
2938 if (not defined $session_id) { $session_id = 0;}
2939 my ($package, $version, $section, $description);
2940 my $PACKAGES;
2941 my $timestamp = &get_time();
2943 if(not stat("$path.in")) {
2944 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2945 return;
2946 }
2948 open($PACKAGES, "<$path.in");
2949 if(not defined($PACKAGES)) {
2950 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2951 return;
2952 }
2954 # Read lines
2955 while (<$PACKAGES>){
2956 my $line = $_;
2957 # Unify
2958 chop($line);
2960 # Use empty lines as a trigger
2961 if ($line =~ /^\s*$/){
2962 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2963 push(@packages_list_statements, $sql);
2964 $package = "none";
2965 $version = "none";
2966 $section = "none";
2967 $description = "none";
2968 next;
2969 }
2971 # Trigger for package name
2972 if ($line =~ /^Package:\s/){
2973 ($package)= ($line =~ /^Package: (.*)$/);
2974 next;
2975 }
2977 # Trigger for version
2978 if ($line =~ /^Version:\s/){
2979 ($version)= ($line =~ /^Version: (.*)$/);
2980 next;
2981 }
2983 # Trigger for description
2984 if ($line =~ /^Description:\s/){
2985 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2986 next;
2987 }
2989 # Trigger for section
2990 if ($line =~ /^Section:\s/){
2991 ($section)= ($line =~ /^Section: (.*)$/);
2992 next;
2993 }
2995 # Trigger for filename
2996 if ($line =~ /^Filename:\s/){
2997 my ($filename) = ($line =~ /^Filename: (.*)$/);
2998 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2999 next;
3000 }
3001 }
3003 close( $PACKAGES );
3004 unlink( "$path.in" );
3005 }
3008 sub store_fileinfo {
3009 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3011 my %fileinfo = (
3012 'package' => $package,
3013 'dist' => $dist,
3014 'version' => $vers,
3015 );
3017 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3018 }
3021 sub cleanup_and_extract {
3022 my $fileinfo = $repo_files{ $File::Find::name };
3024 if( defined $fileinfo ) {
3025 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3026 my $sql;
3027 my $package = $fileinfo->{ 'package' };
3028 my $newver = $fileinfo->{ 'version' };
3030 mkpath($dir);
3031 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3033 if( -f "$dir/DEBIAN/templates" ) {
3035 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3037 my $tmpl= ""; {
3038 local $/=undef;
3039 open FILE, "$dir/DEBIAN/templates";
3040 $tmpl = &encode_base64(<FILE>);
3041 close FILE;
3042 }
3043 rmtree("$dir/DEBIAN/templates");
3045 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3046 push @packages_list_statements, $sql;
3047 }
3048 }
3050 return;
3051 }
3054 sub register_at_foreign_servers {
3055 my ($kernel) = $_[KERNEL];
3057 # hole alle bekannten server aus known_server_db
3058 my $server_sql = "SELECT * FROM $known_server_tn";
3059 my $server_res = $known_server_db->exec_statement($server_sql);
3061 # no entries in known_server_db
3062 if (not ref(@$server_res[0]) eq "ARRAY") {
3063 # TODO
3064 }
3066 # detect already connected clients
3067 my $client_sql = "SELECT * FROM $known_clients_tn";
3068 my $client_res = $known_clients_db->exec_statement($client_sql);
3070 # send my server details to all other gosa-si-server within the network
3071 foreach my $hit (@$server_res) {
3072 my $hostname = @$hit[0];
3073 my $hostkey = &create_passwd;
3075 # add already connected clients to registration message
3076 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3077 &add_content2xml_hash($myhash, 'key', $hostkey);
3078 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3080 # add locally loaded gosa-si modules to registration message
3081 my $loaded_modules = {};
3082 while (my ($package, $pck_info) = each %$known_modules) {
3083 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3084 foreach my $act_module (keys(%{@$pck_info[2]})) {
3085 $loaded_modules->{$act_module} = "";
3086 }
3087 }
3089 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3091 # add macaddress to registration message
3092 my ($host_ip, $host_port) = split(/:/, $hostname);
3093 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3094 my $network_interface= &get_interface_for_ip($local_ip);
3095 my $host_mac = &get_mac_for_interface($network_interface);
3096 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3098 # build registration message and send it
3099 my $foreign_server_msg = &create_xml_string($myhash);
3100 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3101 }
3103 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3104 return;
3105 }
3108 #==== MAIN = main ==============================================================
3109 # parse commandline options
3110 Getopt::Long::Configure( "bundling" );
3111 GetOptions("h|help" => \&usage,
3112 "c|config=s" => \$cfg_file,
3113 "f|foreground" => \$foreground,
3114 "v|verbose+" => \$verbose,
3115 "no-arp+" => \$no_arp,
3116 );
3118 # read and set config parameters
3119 &check_cmdline_param ;
3120 &read_configfile($cfg_file, %cfg_defaults);
3121 &check_pid;
3123 $SIG{CHLD} = 'IGNORE';
3125 # forward error messages to logfile
3126 if( ! $foreground ) {
3127 open( STDIN, '+>/dev/null' );
3128 open( STDOUT, '+>&STDIN' );
3129 open( STDERR, '+>&STDIN' );
3130 }
3132 # Just fork, if we are not in foreground mode
3133 if( ! $foreground ) {
3134 chdir '/' or die "Can't chdir to /: $!";
3135 $pid = fork;
3136 setsid or die "Can't start a new session: $!";
3137 umask 0;
3138 } else {
3139 $pid = $$;
3140 }
3142 # Do something useful - put our PID into the pid_file
3143 if( 0 != $pid ) {
3144 open( LOCK_FILE, ">$pid_file" );
3145 print LOCK_FILE "$pid\n";
3146 close( LOCK_FILE );
3147 if( !$foreground ) {
3148 exit( 0 )
3149 };
3150 }
3152 # parse head url and revision from svn
3153 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3154 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3155 $server_headURL = defined $1 ? $1 : 'unknown' ;
3156 $server_revision = defined $2 ? $2 : 'unknown' ;
3157 if ($server_headURL =~ /\/tag\// ||
3158 $server_headURL =~ /\/branches\// ) {
3159 $server_status = "stable";
3160 } else {
3161 $server_status = "developmental" ;
3162 }
3164 # Prepare log file
3165 $root_uid = getpwnam('root');
3166 $adm_gid = getgrnam('adm');
3167 chmod(0640, $log_file);
3168 chown($root_uid, $adm_gid, $log_file);
3169 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3171 daemon_log(" ", 1);
3172 daemon_log("$0 started!", 1);
3173 daemon_log("status: $server_status", 1);
3174 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3176 {
3177 no strict "refs";
3179 if ($db_module eq "DBmysql") {
3180 # connect to incoming_db
3181 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3182 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3184 # connect to gosa-si job queue
3185 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3186 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3188 # connect to known_clients_db
3189 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3190 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3192 # connect to foreign_clients_db
3193 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3194 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3196 # connect to known_server_db
3197 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3198 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3200 # connect to login_usr_db
3201 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3202 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3204 # connect to fai_server_db
3205 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3206 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3208 # connect to fai_release_db
3209 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3210 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3212 # connect to packages_list_db
3213 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3214 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3216 # connect to messaging_db
3217 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3218 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3220 } elsif ($db_module eq "DBsqlite") {
3221 # connect to incoming_db
3222 unlink($incoming_file_name);
3223 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3224 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3226 # connect to gosa-si job queue
3227 unlink($job_queue_file_name); ## just for debugging
3228 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3229 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3230 chmod(0660, $job_queue_file_name);
3231 chown($root_uid, $adm_gid, $job_queue_file_name);
3233 # connect to known_clients_db
3234 unlink($known_clients_file_name); ## just for debugging
3235 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3236 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3237 chmod(0660, $known_clients_file_name);
3238 chown($root_uid, $adm_gid, $known_clients_file_name);
3240 # connect to foreign_clients_db
3241 unlink($foreign_clients_file_name);
3242 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3243 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3244 chmod(0660, $foreign_clients_file_name);
3245 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3247 # connect to known_server_db
3248 unlink($known_server_file_name);
3249 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3250 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3251 chmod(0660, $known_server_file_name);
3252 chown($root_uid, $adm_gid, $known_server_file_name);
3254 # connect to login_usr_db
3255 unlink($login_users_file_name);
3256 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3257 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3258 chmod(0660, $login_users_file_name);
3259 chown($root_uid, $adm_gid, $login_users_file_name);
3261 # connect to fai_server_db
3262 unlink($fai_server_file_name);
3263 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3264 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3265 chmod(0660, $fai_server_file_name);
3266 chown($root_uid, $adm_gid, $fai_server_file_name);
3268 # connect to fai_release_db
3269 unlink($fai_release_file_name);
3270 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3271 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3272 chmod(0660, $fai_release_file_name);
3273 chown($root_uid, $adm_gid, $fai_release_file_name);
3275 # connect to packages_list_db
3276 #unlink($packages_list_file_name);
3277 unlink($packages_list_under_construction);
3278 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3279 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3280 chmod(0660, $packages_list_file_name);
3281 chown($root_uid, $adm_gid, $packages_list_file_name);
3283 # connect to messaging_db
3284 unlink($messaging_file_name);
3285 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3286 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3287 chmod(0660, $messaging_file_name);
3288 chown($root_uid, $adm_gid, $messaging_file_name);
3290 }
3291 }
3293 # create xml object used for en/decrypting
3294 $xml = new XML::Simple();
3297 # foreign servers
3298 my @foreign_server_list;
3300 # add foreign server from cfg file
3301 if ($foreign_server_string ne "") {
3302 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3303 foreach my $foreign_server (@cfg_foreign_server_list) {
3304 push(@foreign_server_list, $foreign_server);
3305 }
3307 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3308 }
3310 # Perform a DNS lookup for server registration if flag is true
3311 if ($dns_lookup eq "true") {
3312 # Add foreign server from dns
3313 my @tmp_servers;
3314 if (not $server_domain) {
3315 # Try our DNS Searchlist
3316 for my $domain(get_dns_domains()) {
3317 chomp($domain);
3318 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3319 if(@$tmp_domains) {
3320 for my $tmp_server(@$tmp_domains) {
3321 push @tmp_servers, $tmp_server;
3322 }
3323 }
3324 }
3325 if(@tmp_servers && length(@tmp_servers)==0) {
3326 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3327 }
3328 } else {
3329 @tmp_servers = &get_server_addresses($server_domain);
3330 if( 0 == @tmp_servers ) {
3331 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3332 }
3333 }
3335 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3337 foreach my $server (@tmp_servers) {
3338 unshift(@foreign_server_list, $server);
3339 }
3340 } else {
3341 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3342 }
3345 # eliminate duplicate entries
3346 @foreign_server_list = &del_doubles(@foreign_server_list);
3347 my $all_foreign_server = join(", ", @foreign_server_list);
3348 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3350 # add all found foreign servers to known_server
3351 my $act_timestamp = &get_time();
3352 foreach my $foreign_server (@foreign_server_list) {
3354 # do not add myself to known_server_db
3355 if (&is_local($foreign_server)) { next; }
3356 ######################################
3358 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3359 primkey=>['hostname'],
3360 hostname=>$foreign_server,
3361 macaddress=>"",
3362 status=>'not_jet_registered',
3363 hostkey=>"none",
3364 loaded_modules => "none",
3365 timestamp=>$act_timestamp,
3366 } );
3367 }
3370 # Import all modules
3371 &import_modules;
3373 # Check wether all modules are gosa-si valid passwd check
3374 &password_check;
3376 # Prepare for using Opsi
3377 if ($opsi_enabled eq "true") {
3378 use JSON::RPC::Client;
3379 use XML::Quote qw(:all);
3380 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3381 $opsi_client = new JSON::RPC::Client;
3382 }
3385 POE::Component::Server::TCP->new(
3386 Alias => "TCP_SERVER",
3387 Port => $server_port,
3388 ClientInput => sub {
3389 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3390 my $session_id = $session->ID;
3391 my $remote_ip = $heap->{'remote_ip'};
3392 push(@msgs_to_decrypt, $input);
3393 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3394 $kernel->yield("msg_to_decrypt");
3395 },
3396 InlineStates => {
3397 msg_to_decrypt => \&msg_to_decrypt,
3398 next_task => \&next_task,
3399 task_result => \&handle_task_result,
3400 task_done => \&handle_task_done,
3401 task_debug => \&handle_task_debug,
3402 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3403 }
3404 );
3406 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3408 # create session for repeatedly checking the job queue for jobs
3409 POE::Session->create(
3410 inline_states => {
3411 _start => \&session_start,
3412 register_at_foreign_servers => \®ister_at_foreign_servers,
3413 sig_handler => \&sig_handler,
3414 next_task => \&next_task,
3415 task_result => \&handle_task_result,
3416 task_done => \&handle_task_done,
3417 task_debug => \&handle_task_debug,
3418 watch_for_next_tasks => \&watch_for_next_tasks,
3419 watch_for_new_messages => \&watch_for_new_messages,
3420 watch_for_delivery_messages => \&watch_for_delivery_messages,
3421 watch_for_done_messages => \&watch_for_done_messages,
3422 watch_for_new_jobs => \&watch_for_new_jobs,
3423 watch_for_modified_jobs => \&watch_for_modified_jobs,
3424 watch_for_done_jobs => \&watch_for_done_jobs,
3425 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3426 watch_for_old_known_clients => \&watch_for_old_known_clients,
3427 create_packages_list_db => \&run_create_packages_list_db,
3428 create_fai_server_db => \&run_create_fai_server_db,
3429 create_fai_release_db => \&run_create_fai_release_db,
3430 recreate_packages_db => \&run_recreate_packages_db,
3431 session_run_result => \&session_run_result,
3432 session_run_debug => \&session_run_debug,
3433 session_run_done => \&session_run_done,
3434 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3435 }
3436 );
3439 POE::Kernel->run();
3440 exit;