6d2c866d81e8a6128347383392f89760202761a3
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::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $gosa_address;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
101 # dak variables
102 our $dak_base_directory;
103 our $dak_signing_keys_directory;
104 our $dak_queue_directory;
105 our $dak_user;
107 # specifies the verbosity of the daemon_log
108 $verbose = 0 ;
110 # if foreground is not null, script will be not forked to background
111 $foreground = 0 ;
113 # specifies the timeout seconds while checking the online status of a registrating client
114 $ping_timeout = 5;
116 $no_arp = 0;
117 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
118 my @packages_list_statements;
119 my $watch_for_new_jobs_in_progress = 0;
121 # holds all incoming decrypted messages
122 our $incoming_db;
123 our $incoming_tn = 'incoming';
124 my $incoming_file_name;
125 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
126 "timestamp DEFAULT 'none'",
127 "headertag DEFAULT 'none'",
128 "targettag DEFAULT 'none'",
129 "xmlmessage DEFAULT 'none'",
130 "module DEFAULT 'none'",
131 "sessionid DEFAULT '0'",
132 );
134 # holds all gosa jobs
135 our $job_db;
136 our $job_queue_tn = 'jobs';
137 my $job_queue_file_name;
138 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
139 "timestamp DEFAULT 'none'",
140 "status DEFAULT 'none'",
141 "result DEFAULT 'none'",
142 "progress DEFAULT 'none'",
143 "headertag DEFAULT 'none'",
144 "targettag DEFAULT 'none'",
145 "xmlmessage DEFAULT 'none'",
146 "macaddress DEFAULT 'none'",
147 "plainname DEFAULT 'none'",
148 );
150 # holds all other gosa-si-server
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients";
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server";
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release";
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386";
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging";
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
197 "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai";
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 %cfg_defaults = (
210 "general" => {
211 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
212 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
213 },
214 "server" => {
215 "port" => [\$server_port, "20081"],
216 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
217 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
218 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
219 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
220 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
221 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
222 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
223 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
224 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
225 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
226 "repo-path" => [\$repo_path, '/srv/www/repository'],
227 "ldap-uri" => [\$ldap_uri, ""],
228 "ldap-base" => [\$ldap_base, ""],
229 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
230 "ldap-admin-password" => [\$ldap_admin_password, ""],
231 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
232 "max-clients" => [\$max_clients, 10],
233 },
234 "GOsaPackages" => {
235 "ip" => [\$gosa_ip, "0.0.0.0"],
236 "port" => [\$gosa_port, "20082"],
237 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
238 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
239 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
240 "key" => [\$GosaPackages_key, "none"],
241 "dak-base" => [\$dak_base_directory, "/srv/archive"],
242 "dak-keyring" => [\$dak_signing_keys_directory, "/srv/archive/keyrings"],
243 "dak-queue" => [\$dak_queue_directory, "/srv/archive/queue"],
244 "dak-user" => [\$dak_user, "deb-dak"],
245 },
246 "ClientPackages" => {
247 "key" => [\$ClientPackages_key, "none"],
248 },
249 "ServerPackages"=> {
250 "address" => [\$foreign_server_string, ""],
251 "domain" => [\$server_domain, ""],
252 "key" => [\$ServerPackages_key, "none"],
253 "key-lifetime" => [\$foreign_servers_register_delay, 120],
254 }
255 );
258 #=== FUNCTION ================================================================
259 # NAME: usage
260 # PARAMETERS: nothing
261 # RETURNS: nothing
262 # DESCRIPTION: print out usage text to STDERR
263 #===============================================================================
264 sub usage {
265 print STDERR << "EOF" ;
266 usage: $prg [-hvf] [-c config]
268 -h : this (help) message
269 -c <file> : config file
270 -f : foreground, process will not be forked to background
271 -v : be verbose (multiple to increase verbosity)
272 -no-arp : starts $prg without connection to arp module
274 EOF
275 print "\n" ;
276 }
279 #=== FUNCTION ================================================================
280 # NAME: read_configfile
281 # PARAMETERS: cfg_file - string -
282 # RETURNS: nothing
283 # DESCRIPTION: read cfg_file and set variables
284 #===============================================================================
285 sub read_configfile {
286 my $cfg;
287 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
288 if( -r $cfg_file ) {
289 $cfg = Config::IniFiles->new( -file => $cfg_file );
290 } else {
291 print STDERR "Couldn't read config file!\n";
292 }
293 } else {
294 $cfg = Config::IniFiles->new() ;
295 }
296 foreach my $section (keys %cfg_defaults) {
297 foreach my $param (keys %{$cfg_defaults{ $section }}) {
298 my $pinfo = $cfg_defaults{ $section }{ $param };
299 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
300 }
301 }
302 }
305 #=== FUNCTION ================================================================
306 # NAME: logging
307 # PARAMETERS: level - string - default 'info'
308 # msg - string -
309 # facility - string - default 'LOG_DAEMON'
310 # RETURNS: nothing
311 # DESCRIPTION: function for logging
312 #===============================================================================
313 sub daemon_log {
314 # log into log_file
315 my( $msg, $level ) = @_;
316 if(not defined $msg) { return }
317 if(not defined $level) { $level = 1 }
318 if(defined $log_file){
319 open(LOG_HANDLE, ">>$log_file");
320 chmod 0600, $log_file;
321 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
322 print STDERR "cannot open $log_file: $!";
323 return
324 }
325 chomp($msg);
326 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
327 if($level <= $verbose){
328 my ($seconds, $minutes, $hours, $monthday, $month,
329 $year, $weekday, $yearday, $sommertime) = localtime(time);
330 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
331 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
332 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
333 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
334 $month = $monthnames[$month];
335 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
336 $year+=1900;
337 my $name = $prg;
339 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
340 print LOG_HANDLE $log_msg;
341 if( $foreground ) {
342 print STDERR $log_msg;
343 }
344 }
345 close( LOG_HANDLE );
346 }
347 }
350 #=== FUNCTION ================================================================
351 # NAME: check_cmdline_param
352 # PARAMETERS: nothing
353 # RETURNS: nothing
354 # DESCRIPTION: validates commandline parameter
355 #===============================================================================
356 sub check_cmdline_param () {
357 my $err_config;
358 my $err_counter = 0;
359 if(not defined($cfg_file)) {
360 $cfg_file = "/etc/gosa-si/server.conf";
361 if(! -r $cfg_file) {
362 $err_config = "please specify a config file";
363 $err_counter += 1;
364 }
365 }
366 if( $err_counter > 0 ) {
367 &usage( "", 1 );
368 if( defined( $err_config)) { print STDERR "$err_config\n"}
369 print STDERR "\n";
370 exit( -1 );
371 }
372 }
375 #=== FUNCTION ================================================================
376 # NAME: check_pid
377 # PARAMETERS: nothing
378 # RETURNS: nothing
379 # DESCRIPTION: handels pid processing
380 #===============================================================================
381 sub check_pid {
382 $pid = -1;
383 # Check, if we are already running
384 if( open(LOCK_FILE, "<$pid_file") ) {
385 $pid = <LOCK_FILE>;
386 if( defined $pid ) {
387 chomp( $pid );
388 if( -f "/proc/$pid/stat" ) {
389 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
390 if( $stat ) {
391 daemon_log("ERROR: Already running",1);
392 close( LOCK_FILE );
393 exit -1;
394 }
395 }
396 }
397 close( LOCK_FILE );
398 unlink( $pid_file );
399 }
401 # create a syslog msg if it is not to possible to open PID file
402 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
403 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
404 if (open(LOCK_FILE, '<', $pid_file)
405 && ($pid = <LOCK_FILE>))
406 {
407 chomp($pid);
408 $msg .= "(PID $pid)\n";
409 } else {
410 $msg .= "(unable to read PID)\n";
411 }
412 if( ! ($foreground) ) {
413 openlog( $0, "cons,pid", "daemon" );
414 syslog( "warning", $msg );
415 closelog();
416 }
417 else {
418 print( STDERR " $msg " );
419 }
420 exit( -1 );
421 }
422 }
424 #=== FUNCTION ================================================================
425 # NAME: import_modules
426 # PARAMETERS: module_path - string - abs. path to the directory the modules
427 # are stored
428 # RETURNS: nothing
429 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
430 # state is on is imported by "require 'file';"
431 #===============================================================================
432 sub import_modules {
433 daemon_log(" ", 1);
435 if (not -e $modules_path) {
436 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
437 }
439 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
440 while (defined (my $file = readdir (DIR))) {
441 if (not $file =~ /(\S*?).pm$/) {
442 next;
443 }
444 my $mod_name = $1;
446 if( $file =~ /ArpHandler.pm/ ) {
447 if( $no_arp > 0 ) {
448 next;
449 }
450 }
452 eval { require $file; };
453 if ($@) {
454 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
455 daemon_log("$@", 5);
456 } else {
457 my $info = eval($mod_name.'::get_module_info()');
458 # Only load module if get_module_info() returns a non-null object
459 if( $info ) {
460 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
461 $known_modules->{$mod_name} = $info;
462 daemon_log("0 INFO: module $mod_name loaded", 5);
463 }
464 }
465 }
466 close (DIR);
467 }
470 #=== FUNCTION ================================================================
471 # NAME: sig_int_handler
472 # PARAMETERS: signal - string - signal arose from system
473 # RETURNS: noting
474 # DESCRIPTION: handels tasks to be done befor signal becomes active
475 #===============================================================================
476 sub sig_int_handler {
477 my ($signal) = @_;
479 # if (defined($ldap_handle)) {
480 # $ldap_handle->disconnect;
481 # }
482 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
485 daemon_log("shutting down gosa-si-server", 1);
486 system("kill `ps -C gosa-si-server -o pid=`");
487 }
488 $SIG{INT} = \&sig_int_handler;
491 sub check_key_and_xml_validity {
492 my ($crypted_msg, $module_key, $session_id) = @_;
493 my $msg;
494 my $msg_hash;
495 my $error_string;
496 eval{
497 $msg = &decrypt_msg($crypted_msg, $module_key);
499 if ($msg =~ /<xml>/i){
500 $msg =~ s/\s+/ /g; # just for better daemon_log
501 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
502 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
504 ##############
505 # check header
506 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
507 my $header_l = $msg_hash->{'header'};
508 if( 1 > @{$header_l} ) { die 'empty header tag'; }
509 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
510 my $header = @{$header_l}[0];
511 if( 0 == length $header) { die 'empty string in header tag'; }
513 ##############
514 # check source
515 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
516 my $source_l = $msg_hash->{'source'};
517 if( 1 > @{$source_l} ) { die 'empty source tag'; }
518 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
519 my $source = @{$source_l}[0];
520 if( 0 == length $source) { die 'source error'; }
522 ##############
523 # check target
524 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
525 my $target_l = $msg_hash->{'target'};
526 if( 1 > @{$target_l} ) { die 'empty target tag'; }
527 }
528 };
529 if($@) {
530 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
531 $msg = undef;
532 $msg_hash = undef;
533 }
535 return ($msg, $msg_hash);
536 }
539 sub check_outgoing_xml_validity {
540 my ($msg) = @_;
542 my $msg_hash;
543 eval{
544 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
546 ##############
547 # check header
548 my $header_l = $msg_hash->{'header'};
549 if( 1 != @{$header_l} ) {
550 die 'no or more than one headers specified';
551 }
552 my $header = @{$header_l}[0];
553 if( 0 == length $header) {
554 die 'header has length 0';
555 }
557 ##############
558 # check source
559 my $source_l = $msg_hash->{'source'};
560 if( 1 != @{$source_l} ) {
561 die 'no or more than 1 sources specified';
562 }
563 my $source = @{$source_l}[0];
564 if( 0 == length $source) {
565 die 'source has length 0';
566 }
567 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
568 $source =~ /^GOSA$/i ) {
569 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
570 }
572 ##############
573 # check target
574 my $target_l = $msg_hash->{'target'};
575 if( 0 == @{$target_l} ) {
576 die "no targets specified";
577 }
578 foreach my $target (@$target_l) {
579 if( 0 == length $target) {
580 die "target has length 0";
581 }
582 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
583 $target =~ /^GOSA$/i ||
584 $target =~ /^\*$/ ||
585 $target =~ /KNOWN_SERVER/i ||
586 $target =~ /JOBDB/i ||
587 $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 ){
588 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
589 }
590 }
591 };
592 if($@) {
593 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
594 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
595 $msg_hash = undef;
596 }
598 return ($msg_hash);
599 }
602 sub input_from_known_server {
603 my ($input, $remote_ip, $session_id) = @_ ;
604 my ($msg, $msg_hash, $module);
606 my $sql_statement= "SELECT * FROM known_server";
607 my $query_res = $known_server_db->select_dbentry( $sql_statement );
609 while( my ($hit_num, $hit) = each %{ $query_res } ) {
610 my $host_name = $hit->{hostname};
611 if( not $host_name =~ "^$remote_ip") {
612 next;
613 }
614 my $host_key = $hit->{hostkey};
615 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
616 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
618 # check if module can open msg envelope with module key
619 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
620 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
621 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
622 daemon_log("$@", 8);
623 next;
624 }
625 else {
626 $msg = $tmp_msg;
627 $msg_hash = $tmp_msg_hash;
628 $module = "ServerPackages";
629 last;
630 }
631 }
633 if( (!$msg) || (!$msg_hash) || (!$module) ) {
634 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
635 }
637 return ($msg, $msg_hash, $module);
638 }
641 sub input_from_known_client {
642 my ($input, $remote_ip, $session_id) = @_ ;
643 my ($msg, $msg_hash, $module);
645 my $sql_statement= "SELECT * FROM known_clients";
646 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
647 while( my ($hit_num, $hit) = each %{ $query_res } ) {
648 my $host_name = $hit->{hostname};
649 if( not $host_name =~ /^$remote_ip:\d*$/) {
650 next;
651 }
652 my $host_key = $hit->{hostkey};
653 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
654 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
656 # check if module can open msg envelope with module key
657 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
659 if( (!$msg) || (!$msg_hash) ) {
660 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
661 &daemon_log("$@", 8);
662 next;
663 }
664 else {
665 $module = "ClientPackages";
666 last;
667 }
668 }
670 if( (!$msg) || (!$msg_hash) || (!$module) ) {
671 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
672 }
674 return ($msg, $msg_hash, $module);
675 }
678 sub input_from_unknown_host {
679 no strict "refs";
680 my ($input, $session_id) = @_ ;
681 my ($msg, $msg_hash, $module);
682 my $error_string;
684 my %act_modules = %$known_modules;
686 while( my ($mod, $info) = each(%act_modules)) {
688 # check a key exists for this module
689 my $module_key = ${$mod."_key"};
690 if( not defined $module_key ) {
691 if( $mod eq 'ArpHandler' ) {
692 next;
693 }
694 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
695 next;
696 }
697 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
699 # check if module can open msg envelope with module key
700 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
701 if( (not defined $msg) || (not defined $msg_hash) ) {
702 next;
703 }
704 else {
705 $module = $mod;
706 last;
707 }
708 }
710 if( (!$msg) || (!$msg_hash) || (!$module)) {
711 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
712 }
714 return ($msg, $msg_hash, $module);
715 }
718 sub create_ciphering {
719 my ($passwd) = @_;
720 if((!defined($passwd)) || length($passwd)==0) {
721 $passwd = "";
722 }
723 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
724 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
725 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
726 $my_cipher->set_iv($iv);
727 return $my_cipher;
728 }
731 sub encrypt_msg {
732 my ($msg, $key) = @_;
733 my $my_cipher = &create_ciphering($key);
734 my $len;
735 {
736 use bytes;
737 $len= 16-length($msg)%16;
738 }
739 $msg = "\0"x($len).$msg;
740 $msg = $my_cipher->encrypt($msg);
741 chomp($msg = &encode_base64($msg));
742 # there are no newlines allowed inside msg
743 $msg=~ s/\n//g;
744 return $msg;
745 }
748 sub decrypt_msg {
750 my ($msg, $key) = @_ ;
751 $msg = &decode_base64($msg);
752 my $my_cipher = &create_ciphering($key);
753 $msg = $my_cipher->decrypt($msg);
754 $msg =~ s/\0*//g;
755 return $msg;
756 }
759 sub get_encrypt_key {
760 my ($target) = @_ ;
761 my $encrypt_key;
762 my $error = 0;
764 # target can be in known_server
765 if( not defined $encrypt_key ) {
766 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
767 my $query_res = $known_server_db->select_dbentry( $sql_statement );
768 while( my ($hit_num, $hit) = each %{ $query_res } ) {
769 my $host_name = $hit->{hostname};
770 if( $host_name ne $target ) {
771 next;
772 }
773 $encrypt_key = $hit->{hostkey};
774 last;
775 }
776 }
778 # target can be in known_client
779 if( not defined $encrypt_key ) {
780 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
781 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
782 while( my ($hit_num, $hit) = each %{ $query_res } ) {
783 my $host_name = $hit->{hostname};
784 if( $host_name ne $target ) {
785 next;
786 }
787 $encrypt_key = $hit->{hostkey};
788 last;
789 }
790 }
792 return $encrypt_key;
793 }
796 #=== FUNCTION ================================================================
797 # NAME: open_socket
798 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
799 # [PeerPort] string necessary if port not appended by PeerAddr
800 # RETURNS: socket IO::Socket::INET
801 # DESCRIPTION: open a socket to PeerAddr
802 #===============================================================================
803 sub open_socket {
804 my ($PeerAddr, $PeerPort) = @_ ;
805 if(defined($PeerPort)){
806 $PeerAddr = $PeerAddr.":".$PeerPort;
807 }
808 my $socket;
809 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
810 Porto => "tcp",
811 Type => SOCK_STREAM,
812 Timeout => 5,
813 );
814 if(not defined $socket) {
815 return;
816 }
817 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
818 return $socket;
819 }
822 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
823 #=== FUNCTION ================================================================
824 # NAME: get_ip
825 # PARAMETERS: interface name (i.e. eth0)
826 # RETURNS: (ip address)
827 # DESCRIPTION: Uses ioctl to get ip address directly from system.
828 #===============================================================================
829 #sub get_ip {
830 # my $ifreq= shift;
831 # my $result= "";
832 # my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
833 # my $proto= getprotobyname('ip');
834 #
835 # socket SOCKET, PF_INET, SOCK_DGRAM, $proto
836 # or die "socket: $!";
837 #
838 # if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
839 # my ($if, $sin) = unpack 'a16 a16', $ifreq;
840 # my ($port, $addr) = sockaddr_in $sin;
841 # my $ip = inet_ntoa $addr;
842 #
843 # if ($ip && length($ip) > 0) {
844 # $result = $ip;
845 # }
846 # }
847 #
848 # return $result;
849 #}
852 sub get_local_ip_for_remote_ip {
853 my $remote_ip= shift;
854 my $result="0.0.0.0";
856 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
857 if($remote_ip eq "127.0.0.1") {
858 $result = "127.0.0.1";
859 } else {
860 my $PROC_NET_ROUTE= ('/proc/net/route');
862 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
863 or die "Could not open $PROC_NET_ROUTE";
865 my @ifs = <PROC_NET_ROUTE>;
867 close(PROC_NET_ROUTE);
869 # Eat header line
870 shift @ifs;
871 chomp @ifs;
872 foreach my $line(@ifs) {
873 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
874 my $destination;
875 my $mask;
876 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
877 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
879 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
880 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
881 # destination matches route, save mac and exit
882 $result= &get_ip($Iface);
883 last;
884 }
885 }
886 }
887 } else {
888 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
889 }
890 return $result;
891 }
894 sub send_msg_to_target {
895 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
896 my $error = 0;
897 my $header;
898 my $timestamp = &get_time();
899 my $new_status;
900 my $act_status;
901 my ($sql_statement, $res);
903 if( $msg_header ) {
904 $header = "'$msg_header'-";
905 } else {
906 $header = "";
907 }
909 # Patch the source ip
910 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
911 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
912 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
913 }
915 # encrypt xml msg
916 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
918 # opensocket
919 my $socket = &open_socket($address);
920 if( !$socket ) {
921 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
922 $error++;
923 }
925 if( $error == 0 ) {
926 # send xml msg
927 print $socket $crypted_msg."\n";
929 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
930 daemon_log("$session_id DEBUG: message:\n$msg", 9);
932 }
934 # close socket in any case
935 if( $socket ) {
936 close $socket;
937 }
939 if( $error > 0 ) { $new_status = "down"; }
940 else { $new_status = $msg_header; }
943 # known_clients
944 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
945 $res = $known_clients_db->select_dbentry($sql_statement);
946 if( keys(%$res) == 1) {
947 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
948 if ($act_status eq "down" && $new_status eq "down") {
949 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
950 $res = $known_clients_db->del_dbentry($sql_statement);
951 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
952 } else {
953 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
954 $res = $known_clients_db->update_dbentry($sql_statement);
955 if($new_status eq "down"){
956 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957 } else {
958 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
959 }
960 }
961 }
963 # known_server
964 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
965 $res = $known_server_db->select_dbentry($sql_statement);
966 if( keys(%$res) == 1) {
967 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
968 if ($act_status eq "down" && $new_status eq "down") {
969 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
970 $res = $known_server_db->del_dbentry($sql_statement);
971 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
972 }
973 else {
974 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
975 $res = $known_server_db->update_dbentry($sql_statement);
976 if($new_status eq "down"){
977 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
978 } else {
979 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
980 }
981 }
982 }
983 return $error;
984 }
987 sub update_jobdb_status_for_send_msgs {
988 my ($answer, $error) = @_;
989 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
990 my $jobdb_id = $1;
992 # sending msg faild
993 if( $error ) {
994 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
995 my $sql_statement = "UPDATE $job_queue_tn ".
996 "SET status='error', result='can not deliver msg, please consult log file' ".
997 "WHERE id=$jobdb_id";
998 my $res = $job_db->update_dbentry($sql_statement);
999 }
1001 # sending msg was successful
1002 } else {
1003 my $sql_statement = "UPDATE $job_queue_tn ".
1004 "SET status='done' ".
1005 "WHERE id=$jobdb_id AND status='processed'";
1006 my $res = $job_db->update_dbentry($sql_statement);
1007 }
1008 }
1009 }
1012 sub sig_handler {
1013 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1014 daemon_log("0 INFO got signal '$signal'", 1);
1015 $kernel->sig_handled();
1016 return;
1017 }
1020 sub msg_to_decrypt {
1021 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1022 my $session_id = $session->ID;
1023 my ($msg, $msg_hash, $module);
1024 my $error = 0;
1026 # hole neue msg aus @msgs_to_decrypt
1027 my $next_msg = shift @msgs_to_decrypt;
1029 # entschlüssle sie
1031 # msg is from a new client or gosa
1032 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1033 # msg is from a gosa-si-server
1034 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1035 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1036 }
1037 # msg is from a gosa-si-client
1038 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1039 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1040 }
1041 # an error occurred
1042 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1043 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1044 # could not understand a msg from its server the client cause a re-registering process
1045 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1046 "' to cause a re-registering of the client if necessary", 5);
1047 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1048 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1049 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1050 my $host_name = $hit->{'hostname'};
1051 my $host_key = $hit->{'hostkey'};
1052 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1053 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1054 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1055 }
1056 $error++;
1057 }
1060 my $header;
1061 my $target;
1062 my $source;
1063 my $done = 0;
1064 my $sql;
1065 my $res;
1067 # check whether this message should be processed here
1068 if ($error == 0) {
1069 $header = @{$msg_hash->{'header'}}[0];
1070 $target = @{$msg_hash->{'target'}}[0];
1071 $source = @{$msg_hash->{'source'}}[0];
1072 my $not_found_in_known_clients_db = 0;
1073 my $not_found_in_known_server_db = 0;
1074 my $not_found_in_foreign_clients_db = 0;
1075 my $local_address;
1076 my ($target_ip, $target_port) = split(':', $target);
1077 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1078 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1079 } else {
1080 $local_address = $server_address;
1081 }
1083 # target and source is equal to GOSA -> process here
1084 if (not $done) {
1085 if ($target eq "GOSA" && $source eq "GOSA") {
1086 $done = 1;
1087 }
1088 }
1090 # target is own address without forward_to_gosa-tag -> process here
1091 if (not $done) {
1092 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1093 $done = 1;
1094 if ($source eq "GOSA") {
1095 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1096 }
1097 #print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1098 }
1099 }
1101 # target is a client address in known_clients -> process here
1102 if (not $done) {
1103 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1104 $res = $known_clients_db->select_dbentry($sql);
1105 if (keys(%$res) > 0) {
1106 $done = 1;
1107 my $hostname = $res->{1}->{'hostname'};
1108 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1109 #print STDERR "target is a client address in known_clients -> process here\n";
1110 } else {
1111 $not_found_in_known_clients_db = 1;
1112 }
1113 }
1115 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1116 if (not $done) {
1117 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1118 my $gosa_at;
1119 my $gosa_session_id;
1120 if (($target eq $local_address) && (defined $forward_to_gosa)){
1121 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1122 if ($gosa_at ne $local_address) {
1123 $done = 1;
1124 #print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1125 }
1126 }
1127 }
1129 # if message should be processed here -> add message to incoming_db
1130 if ($done) {
1131 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1132 # so gosa-si-server knows how to process this kind of messages
1133 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1134 $module = "GosaPackages";
1135 }
1137 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1138 primkey=>[],
1139 headertag=>$header,
1140 targettag=>$target,
1141 xmlmessage=>&encode_base64($msg),
1142 timestamp=>&get_time,
1143 module=>$module,
1144 sessionid=>$session_id,
1145 } );
1146 }
1148 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1149 if (not $done) {
1150 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1151 my $gosa_at;
1152 my $gosa_session_id;
1153 if (($target eq $local_address) && (defined $forward_to_gosa)){
1154 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1155 if ($gosa_at eq $local_address) {
1156 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1157 if( defined $session_reference ) {
1158 $heap = $session_reference->get_heap();
1159 }
1160 if(exists $heap->{'client'}) {
1161 $msg = &encrypt_msg($msg, $GosaPackages_key);
1162 $heap->{'client'}->put($msg);
1163 }
1164 $done = 1;
1165 #print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1166 }
1167 }
1169 }
1171 # target is a client address in foreign_clients -> forward to registration server
1172 if (not $done) {
1173 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1174 $res = $foreign_clients_db->select_dbentry($sql);
1175 if (keys(%$res) > 0) {
1176 my $hostname = $res->{1}->{'hostname'};
1177 my $regserver = $res->{1}->{'regserver'};
1178 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1179 my $res = $known_server_db->select_dbentry($sql);
1180 if (keys(%$res) > 0) {
1181 my $regserver_key = $res->{1}->{'hostkey'};
1182 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1183 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1184 if ($source eq "GOSA") {
1185 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1186 }
1187 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1188 }
1189 $done = 1;
1190 #print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1191 } else {
1192 $not_found_in_foreign_clients_db = 1;
1193 }
1194 }
1196 # target is a server address -> forward to server
1197 if (not $done) {
1198 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1199 $res = $known_server_db->select_dbentry($sql);
1200 if (keys(%$res) > 0) {
1201 my $hostkey = $res->{1}->{'hostkey'};
1203 if ($source eq "GOSA") {
1204 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1205 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1207 }
1209 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1210 $done = 1;
1211 #print STDERR "target is a server address -> forward to server\n";
1212 } else {
1213 $not_found_in_known_server_db = 1;
1214 }
1215 }
1218 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1219 if ( $not_found_in_foreign_clients_db
1220 && $not_found_in_known_server_db
1221 && $not_found_in_known_clients_db) {
1222 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1223 primkey=>[],
1224 headertag=>$header,
1225 targettag=>$target,
1226 xmlmessage=>&encode_base64($msg),
1227 timestamp=>&get_time,
1228 module=>$module,
1229 sessionid=>$session_id,
1230 } );
1231 $done = 1;
1232 }
1235 if (not $done) {
1236 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1237 if ($source eq "GOSA") {
1238 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1239 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1241 my $session_reference = $kernel->ID_id_to_session($session_id);
1242 if( defined $session_reference ) {
1243 $heap = $session_reference->get_heap();
1244 }
1245 if(exists $heap->{'client'}) {
1246 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1247 $heap->{'client'}->put($error_msg);
1248 }
1249 }
1250 }
1252 }
1254 return;
1255 }
1258 sub next_task {
1259 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1260 my $running_task = POE::Wheel::Run->new(
1261 Program => sub { process_task($session, $heap, $task) },
1262 StdioFilter => POE::Filter::Reference->new(),
1263 StdoutEvent => "task_result",
1264 StderrEvent => "task_debug",
1265 CloseEvent => "task_done",
1266 );
1267 $heap->{task}->{ $running_task->ID } = $running_task;
1268 }
1270 sub handle_task_result {
1271 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1272 my $client_answer = $result->{'answer'};
1273 if( $client_answer =~ s/session_id=(\d+)$// ) {
1274 my $session_id = $1;
1275 if( defined $session_id ) {
1276 my $session_reference = $kernel->ID_id_to_session($session_id);
1277 if( defined $session_reference ) {
1278 $heap = $session_reference->get_heap();
1279 }
1280 }
1282 if(exists $heap->{'client'}) {
1283 $heap->{'client'}->put($client_answer);
1284 }
1285 }
1286 $kernel->sig(CHLD => "child_reap");
1287 }
1289 sub handle_task_debug {
1290 my $result = $_[ARG0];
1291 print STDERR "$result\n";
1292 }
1294 sub handle_task_done {
1295 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1296 delete $heap->{task}->{$task_id};
1297 }
1299 sub process_task {
1300 no strict "refs";
1301 my ($session, $heap, $task) = @_;
1302 my $error = 0;
1303 my $answer_l;
1304 my ($answer_header, @answer_target_l, $answer_source);
1305 my $client_answer = "";
1307 # prepare all variables needed to process message
1308 #my $msg = $task->{'xmlmessage'};
1309 my $msg = &decode_base64($task->{'xmlmessage'});
1310 my $incoming_id = $task->{'id'};
1311 my $module = $task->{'module'};
1312 my $header = $task->{'headertag'};
1313 my $session_id = $task->{'sessionid'};
1314 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1315 my $source = @{$msg_hash->{'source'}}[0];
1317 # set timestamp of incoming client uptodate, so client will not
1318 # be deleted from known_clients because of expiration
1319 my $act_time = &get_time();
1320 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1321 my $res = $known_clients_db->exec_statement($sql);
1323 ######################
1324 # process incoming msg
1325 if( $error == 0) {
1326 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1327 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1328 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1330 if ( 0 < @{$answer_l} ) {
1331 my $answer_str = join("\n", @{$answer_l});
1332 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1333 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1334 }
1335 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1336 } else {
1337 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1338 }
1340 }
1341 if( !$answer_l ) { $error++ };
1343 ########
1344 # answer
1345 if( $error == 0 ) {
1347 foreach my $answer ( @{$answer_l} ) {
1348 # check outgoing msg to xml validity
1349 my $answer_hash = &check_outgoing_xml_validity($answer);
1350 if( not defined $answer_hash ) { next; }
1352 $answer_header = @{$answer_hash->{'header'}}[0];
1353 @answer_target_l = @{$answer_hash->{'target'}};
1354 $answer_source = @{$answer_hash->{'source'}}[0];
1356 # deliver msg to all targets
1357 foreach my $answer_target ( @answer_target_l ) {
1359 # targets of msg are all gosa-si-clients in known_clients_db
1360 if( $answer_target eq "*" ) {
1361 # answer is for all clients
1362 my $sql_statement= "SELECT * FROM known_clients";
1363 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1364 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1365 my $host_name = $hit->{hostname};
1366 my $host_key = $hit->{hostkey};
1367 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1368 &update_jobdb_status_for_send_msgs($answer, $error);
1369 }
1370 }
1372 # targets of msg are all gosa-si-server in known_server_db
1373 elsif( $answer_target eq "KNOWN_SERVER" ) {
1374 # answer is for all server in known_server
1375 my $sql_statement= "SELECT * FROM $known_server_tn";
1376 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1377 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1378 my $host_name = $hit->{hostname};
1379 my $host_key = $hit->{hostkey};
1380 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1381 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1382 &update_jobdb_status_for_send_msgs($answer, $error);
1383 }
1384 }
1386 # target of msg is GOsa
1387 elsif( $answer_target eq "GOSA" ) {
1388 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1389 my $add_on = "";
1390 if( defined $session_id ) {
1391 $add_on = ".session_id=$session_id";
1392 }
1393 # answer is for GOSA and has to returned to connected client
1394 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1395 $client_answer = $gosa_answer.$add_on;
1396 }
1398 # target of msg is job queue at this host
1399 elsif( $answer_target eq "JOBDB") {
1400 $answer =~ /<header>(\S+)<\/header>/;
1401 my $header;
1402 if( defined $1 ) { $header = $1; }
1403 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1404 &update_jobdb_status_for_send_msgs($answer, $error);
1405 }
1407 # target of msg is a mac address
1408 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 ) {
1409 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1410 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1411 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1412 my $found_ip_flag = 0;
1413 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1414 my $host_name = $hit->{hostname};
1415 my $host_key = $hit->{hostkey};
1416 $answer =~ s/$answer_target/$host_name/g;
1417 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
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 $found_ip_flag++ ;
1421 }
1422 if( $found_ip_flag == 0) {
1423 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1424 }
1426 # answer is for one specific host
1427 } else {
1428 # get encrypt_key
1429 my $encrypt_key = &get_encrypt_key($answer_target);
1430 if( not defined $encrypt_key ) {
1431 # unknown target
1432 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1433 next;
1434 }
1435 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1436 &update_jobdb_status_for_send_msgs($answer, $error);
1437 }
1438 }
1439 }
1440 }
1442 my $filter = POE::Filter::Reference->new();
1443 my %result = (
1444 status => "seems ok to me",
1445 answer => $client_answer,
1446 );
1448 my $output = $filter->put( [ \%result ] );
1449 print @$output;
1452 }
1454 sub session_start {
1455 my ($kernel) = $_[KERNEL];
1456 $global_kernel = $kernel;
1457 $kernel->yield('register_at_foreign_servers');
1458 $kernel->yield('create_fai_server_db', $fai_server_tn );
1459 $kernel->yield('create_fai_release_db', $fai_release_tn );
1460 $kernel->yield('watch_for_next_tasks');
1461 $kernel->sig(USR1 => "sig_handler");
1462 $kernel->sig(USR2 => "recreate_packages_db");
1463 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1464 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1465 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1466 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1467 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1468 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1470 }
1473 sub watch_for_done_jobs {
1474 my ($kernel,$heap) = @_[KERNEL, HEAP];
1476 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1477 my $res = $job_db->select_dbentry( $sql_statement );
1479 while( my ($id, $hit) = each %{$res} ) {
1480 my $jobdb_id = $hit->{id};
1481 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1482 my $res = $job_db->del_dbentry($sql_statement);
1483 }
1485 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1486 }
1489 sub watch_for_new_jobs {
1490 if($watch_for_new_jobs_in_progress == 0) {
1491 $watch_for_new_jobs_in_progress = 1;
1492 my ($kernel,$heap) = @_[KERNEL, HEAP];
1494 # check gosa job queue for jobs with executable timestamp
1495 my $timestamp = &get_time();
1496 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1497 my $res = $job_db->exec_statement( $sql_statement );
1499 # Merge all new jobs that would do the same actions
1500 my @drops;
1501 my $hits;
1502 foreach my $hit (reverse @{$res} ) {
1503 my $macaddress= lc @{$hit}[8];
1504 my $headertag= @{$hit}[5];
1505 if(
1506 defined($hits->{$macaddress}) &&
1507 defined($hits->{$macaddress}->{$headertag}) &&
1508 defined($hits->{$macaddress}->{$headertag}[0])
1509 ) {
1510 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1511 }
1512 $hits->{$macaddress}->{$headertag}= $hit;
1513 }
1515 # Delete new jobs with a matching job in state 'processing'
1516 foreach my $macaddress (keys %{$hits}) {
1517 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1518 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1519 if(defined($jobdb_id)) {
1520 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1521 my $res = $job_db->exec_statement( $sql_statement );
1522 foreach my $hit (@{$res}) {
1523 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1524 }
1525 } else {
1526 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1527 }
1528 }
1529 }
1531 # Commit deletion
1532 $job_db->exec_statementlist(\@drops);
1534 # Look for new jobs that could be executed
1535 foreach my $macaddress (keys %{$hits}) {
1537 # Look if there is an executing job
1538 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1539 my $res = $job_db->exec_statement( $sql_statement );
1541 # Skip new jobs for host if there is a processing job
1542 if(defined($res) and defined @{$res}[0]) {
1543 next;
1544 }
1546 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1547 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1548 if(defined($jobdb_id)) {
1549 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1551 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1552 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1553 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1555 # expect macaddress is unique!!!!!!
1556 my $target = $res_hash->{1}->{hostname};
1558 # change header
1559 $job_msg =~ s/<header>job_/<header>gosa_/;
1561 # add sqlite_id
1562 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1564 $job_msg =~ /<header>(\S+)<\/header>/;
1565 my $header = $1 ;
1566 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1568 # update status in job queue to 'processing'
1569 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1570 my $res = $job_db->update_dbentry($sql_statement);
1571 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1573 # We don't want parallel processing
1574 last;
1575 }
1576 }
1577 }
1579 $watch_for_new_jobs_in_progress = 0;
1580 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1581 }
1582 }
1585 sub watch_for_new_messages {
1586 my ($kernel,$heap) = @_[KERNEL, HEAP];
1587 my @coll_user_msg; # collection list of outgoing messages
1589 # check messaging_db for new incoming messages with executable timestamp
1590 my $timestamp = &get_time();
1591 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1592 my $res = $messaging_db->exec_statement( $sql_statement );
1593 foreach my $hit (@{$res}) {
1595 # create outgoing messages
1596 my $message_to = @{$hit}[3];
1597 # translate message_to to plain login name
1598 my @message_to_l = split(/,/, $message_to);
1599 my %receiver_h;
1600 foreach my $receiver (@message_to_l) {
1601 if ($receiver =~ /^u_([\s\S]*)$/) {
1602 $receiver_h{$1} = 0;
1603 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1604 my $group_name = $1;
1605 # fetch all group members from ldap and add them to receiver hash
1606 my $ldap_handle = &get_ldap_handle();
1607 if (defined $ldap_handle) {
1608 my $mesg = $ldap_handle->search(
1609 base => $ldap_base,
1610 scope => 'sub',
1611 attrs => ['memberUid'],
1612 filter => "cn=$group_name",
1613 );
1614 if ($mesg->count) {
1615 my @entries = $mesg->entries;
1616 foreach my $entry (@entries) {
1617 my @receivers= $entry->get_value("memberUid");
1618 foreach my $receiver (@receivers) {
1619 $receiver_h{$1} = 0;
1620 }
1621 }
1622 }
1623 # translating errors ?
1624 if ($mesg->code) {
1625 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1626 }
1627 # ldap handle error ?
1628 } else {
1629 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1630 }
1631 } else {
1632 my $sbjct = &encode_base64(@{$hit}[1]);
1633 my $msg = &encode_base64(@{$hit}[7]);
1634 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1635 }
1636 }
1637 my @receiver_l = keys(%receiver_h);
1639 my $message_id = @{$hit}[0];
1641 #add each outgoing msg to messaging_db
1642 my $receiver;
1643 foreach $receiver (@receiver_l) {
1644 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1645 "VALUES ('".
1646 $message_id."', '". # id
1647 @{$hit}[1]."', '". # subject
1648 @{$hit}[2]."', '". # message_from
1649 $receiver."', '". # message_to
1650 "none"."', '". # flag
1651 "out"."', '". # direction
1652 @{$hit}[6]."', '". # delivery_time
1653 @{$hit}[7]."', '". # message
1654 $timestamp."'". # timestamp
1655 ")";
1656 &daemon_log("M DEBUG: $sql_statement", 1);
1657 my $res = $messaging_db->exec_statement($sql_statement);
1658 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1659 }
1661 # set incoming message to flag d=deliverd
1662 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1663 &daemon_log("M DEBUG: $sql_statement", 7);
1664 $res = $messaging_db->update_dbentry($sql_statement);
1665 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1666 }
1668 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1669 return;
1670 }
1672 sub watch_for_delivery_messages {
1673 my ($kernel, $heap) = @_[KERNEL, HEAP];
1675 # select outgoing messages
1676 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1677 #&daemon_log("0 DEBUG: $sql", 7);
1678 my $res = $messaging_db->exec_statement( $sql_statement );
1680 # build out msg for each usr
1681 foreach my $hit (@{$res}) {
1682 my $receiver = @{$hit}[3];
1683 my $msg_id = @{$hit}[0];
1684 my $subject = @{$hit}[1];
1685 my $message = @{$hit}[7];
1687 # resolve usr -> host where usr is logged in
1688 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1689 #&daemon_log("0 DEBUG: $sql", 7);
1690 my $res = $login_users_db->exec_statement($sql);
1692 # reciver is logged in nowhere
1693 if (not ref(@$res[0]) eq "ARRAY") { next; }
1695 my $send_succeed = 0;
1696 foreach my $hit (@$res) {
1697 my $receiver_host = @$hit[0];
1698 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1700 # fetch key to encrypt msg propperly for usr/host
1701 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1702 &daemon_log("0 DEBUG: $sql", 7);
1703 my $res = $known_clients_db->exec_statement($sql);
1705 # host is already down
1706 if (not ref(@$res[0]) eq "ARRAY") { next; }
1708 # host is on
1709 my $receiver_key = @{@{$res}[0]}[2];
1710 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1711 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1712 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1713 if ($error == 0 ) {
1714 $send_succeed++ ;
1715 }
1716 }
1718 if ($send_succeed) {
1719 # set outgoing msg at db to deliverd
1720 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1721 &daemon_log("0 DEBUG: $sql", 7);
1722 my $res = $messaging_db->exec_statement($sql);
1723 }
1724 }
1726 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1727 return;
1728 }
1731 sub watch_for_done_messages {
1732 my ($kernel,$heap) = @_[KERNEL, HEAP];
1734 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1735 #&daemon_log("0 DEBUG: $sql", 7);
1736 my $res = $messaging_db->exec_statement($sql);
1738 foreach my $hit (@{$res}) {
1739 my $msg_id = @{$hit}[0];
1741 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1742 #&daemon_log("0 DEBUG: $sql", 7);
1743 my $res = $messaging_db->exec_statement($sql);
1745 # not all usr msgs have been seen till now
1746 if ( ref(@$res[0]) eq "ARRAY") { next; }
1748 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1749 #&daemon_log("0 DEBUG: $sql", 7);
1750 $res = $messaging_db->exec_statement($sql);
1752 }
1754 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1755 return;
1756 }
1759 sub watch_for_old_known_clients {
1760 my ($kernel,$heap) = @_[KERNEL, HEAP];
1762 my $sql_statement = "SELECT * FROM $known_clients_tn";
1763 my $res = $known_clients_db->select_dbentry( $sql_statement );
1765 my $act_time = int(&get_time());
1767 while ( my ($hit_num, $hit) = each %$res) {
1768 my $expired_timestamp = int($hit->{'timestamp'});
1769 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1770 my $dt = DateTime->new( year => $1,
1771 month => $2,
1772 day => $3,
1773 hour => $4,
1774 minute => $5,
1775 second => $6,
1776 );
1778 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1779 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1780 if ($act_time > $expired_timestamp) {
1781 my $hostname = $hit->{'hostname'};
1782 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1783 my $del_res = $known_clients_db->exec_statement($del_sql);
1785 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1786 }
1788 }
1790 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1791 }
1794 sub watch_for_next_tasks {
1795 my ($kernel,$heap) = @_[KERNEL, HEAP];
1797 my $sql = "SELECT * FROM $incoming_tn";
1798 my $res = $incoming_db->select_dbentry($sql);
1800 while ( my ($hit_num, $hit) = each %$res) {
1801 my $headertag = $hit->{'headertag'};
1802 if ($headertag =~ /^answer_(\d+)/) {
1803 # do not start processing, this message is for a still running POE::Wheel
1804 next;
1805 }
1806 my $message_id = $hit->{'id'};
1807 $kernel->yield('next_task', $hit);
1809 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1810 my $res = $incoming_db->exec_statement($sql);
1811 }
1813 $kernel->delay_set('watch_for_next_tasks', 0.1);
1814 }
1817 sub get_ldap_handle {
1818 my ($session_id) = @_;
1819 my $heap;
1820 my $ldap_handle;
1822 if (not defined $session_id ) { $session_id = 0 };
1823 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1825 if ($session_id == 0) {
1826 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1827 $ldap_handle = Net::LDAP->new( $ldap_uri );
1828 $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!");
1830 } else {
1831 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1832 if( defined $session_reference ) {
1833 $heap = $session_reference->get_heap();
1834 }
1836 if (not defined $heap) {
1837 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1838 return;
1839 }
1841 # TODO: This "if" is nonsense, because it doesn't prove that the
1842 # used handle is still valid - or if we've to reconnect...
1843 #if (not exists $heap->{ldap_handle}) {
1844 $ldap_handle = Net::LDAP->new( $ldap_uri );
1845 $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!");
1846 $heap->{ldap_handle} = $ldap_handle;
1847 #}
1848 }
1849 return $ldap_handle;
1850 }
1853 sub change_fai_state {
1854 my ($st, $targets, $session_id) = @_;
1855 $session_id = 0 if not defined $session_id;
1856 # Set FAI state to localboot
1857 my %mapActions= (
1858 reboot => '',
1859 update => 'softupdate',
1860 localboot => 'localboot',
1861 reinstall => 'install',
1862 rescan => '',
1863 wake => '',
1864 memcheck => 'memcheck',
1865 sysinfo => 'sysinfo',
1866 install => 'install',
1867 );
1869 # Return if this is unknown
1870 if (!exists $mapActions{ $st }){
1871 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1872 return;
1873 }
1875 my $state= $mapActions{ $st };
1877 my $ldap_handle = &get_ldap_handle($session_id);
1878 if( defined($ldap_handle) ) {
1880 # Build search filter for hosts
1881 my $search= "(&(objectClass=GOhard)";
1882 foreach (@{$targets}){
1883 $search.= "(macAddress=$_)";
1884 }
1885 $search.= ")";
1887 # If there's any host inside of the search string, procress them
1888 if (!($search =~ /macAddress/)){
1889 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1890 return;
1891 }
1893 # Perform search for Unit Tag
1894 my $mesg = $ldap_handle->search(
1895 base => $ldap_base,
1896 scope => 'sub',
1897 attrs => ['dn', 'FAIstate', 'objectClass'],
1898 filter => "$search"
1899 );
1901 if ($mesg->count) {
1902 my @entries = $mesg->entries;
1903 if (0 == @entries) {
1904 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1905 }
1907 foreach my $entry (@entries) {
1908 # Only modify entry if it is not set to '$state'
1909 if ($entry->get_value("FAIstate") ne "$state"){
1910 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1911 my $result;
1912 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1913 if (exists $tmp{'FAIobject'}){
1914 if ($state eq ''){
1915 $result= $ldap_handle->modify($entry->dn, changes => [
1916 delete => [ FAIstate => [] ] ]);
1917 } else {
1918 $result= $ldap_handle->modify($entry->dn, changes => [
1919 replace => [ FAIstate => $state ] ]);
1920 }
1921 } elsif ($state ne ''){
1922 $result= $ldap_handle->modify($entry->dn, changes => [
1923 add => [ objectClass => 'FAIobject' ],
1924 add => [ FAIstate => $state ] ]);
1925 }
1927 # Errors?
1928 if ($result->code){
1929 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1930 }
1931 } else {
1932 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1933 }
1934 }
1935 } else {
1936 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1937 }
1939 # if no ldap handle defined
1940 } else {
1941 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1942 }
1944 return;
1945 }
1948 sub change_goto_state {
1949 my ($st, $targets, $session_id) = @_;
1950 $session_id = 0 if not defined $session_id;
1952 # Switch on or off?
1953 my $state= $st eq 'active' ? 'active': 'locked';
1955 my $ldap_handle = &get_ldap_handle($session_id);
1956 if( defined($ldap_handle) ) {
1958 # Build search filter for hosts
1959 my $search= "(&(objectClass=GOhard)";
1960 foreach (@{$targets}){
1961 $search.= "(macAddress=$_)";
1962 }
1963 $search.= ")";
1965 # If there's any host inside of the search string, procress them
1966 if (!($search =~ /macAddress/)){
1967 return;
1968 }
1970 # Perform search for Unit Tag
1971 my $mesg = $ldap_handle->search(
1972 base => $ldap_base,
1973 scope => 'sub',
1974 attrs => ['dn', 'gotoMode'],
1975 filter => "$search"
1976 );
1978 if ($mesg->count) {
1979 my @entries = $mesg->entries;
1980 foreach my $entry (@entries) {
1982 # Only modify entry if it is not set to '$state'
1983 if ($entry->get_value("gotoMode") ne $state){
1985 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1986 my $result;
1987 $result= $ldap_handle->modify($entry->dn, changes => [
1988 replace => [ gotoMode => $state ] ]);
1990 # Errors?
1991 if ($result->code){
1992 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1993 }
1995 }
1996 }
1997 } else {
1998 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
1999 }
2001 }
2002 }
2005 sub run_recreate_packages_db {
2006 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2007 my $session_id = $session->ID;
2008 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 4);
2009 $kernel->yield('create_fai_release_db');
2010 $kernel->yield('create_fai_server_db');
2011 return;
2012 }
2015 sub run_create_fai_server_db {
2016 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2017 my $session_id = $session->ID;
2018 my $task = POE::Wheel::Run->new(
2019 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2020 StdoutEvent => "session_run_result",
2021 StderrEvent => "session_run_debug",
2022 CloseEvent => "session_run_done",
2023 );
2025 $heap->{task}->{ $task->ID } = $task;
2026 return;
2027 }
2030 sub create_fai_server_db {
2031 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2032 my $result;
2034 if (not defined $session_id) { $session_id = 0; }
2035 my $ldap_handle = &get_ldap_handle();
2036 if(defined($ldap_handle)) {
2037 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2038 my $mesg= $ldap_handle->search(
2039 base => $ldap_base,
2040 scope => 'sub',
2041 attrs => ['FAIrepository', 'gosaUnitTag'],
2042 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2043 );
2044 if($mesg->{'resultCode'} == 0 &&
2045 $mesg->count != 0) {
2046 foreach my $entry (@{$mesg->{entries}}) {
2047 if($entry->exists('FAIrepository')) {
2048 # Add an entry for each Repository configured for server
2049 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2050 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2051 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2052 $result= $fai_server_db->add_dbentry( {
2053 table => $table_name,
2054 primkey => ['server', 'release', 'tag'],
2055 server => $tmp_url,
2056 release => $tmp_release,
2057 sections => $tmp_sections,
2058 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2059 } );
2060 }
2061 }
2062 }
2063 }
2064 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2066 # TODO: Find a way to post the 'create_packages_list_db' event
2067 if(not defined($dont_create_packages_list)) {
2068 &create_packages_list_db(undef, undef, $session_id);
2069 }
2070 }
2072 $ldap_handle->disconnect;
2073 return $result;
2074 }
2077 sub run_create_fai_release_db {
2078 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2079 my $session_id = $session->ID;
2080 my $task = POE::Wheel::Run->new(
2081 Program => sub { &create_fai_release_db($table_name, $session_id) },
2082 StdoutEvent => "session_run_result",
2083 StderrEvent => "session_run_debug",
2084 CloseEvent => "session_run_done",
2085 );
2087 $heap->{task}->{ $task->ID } = $task;
2088 return;
2089 }
2092 sub create_fai_release_db {
2093 my ($table_name, $session_id) = @_;
2094 my $result;
2096 # used for logging
2097 if (not defined $session_id) { $session_id = 0; }
2099 my $ldap_handle = &get_ldap_handle();
2100 if(defined($ldap_handle)) {
2101 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2102 my $mesg= $ldap_handle->search(
2103 base => $ldap_base,
2104 scope => 'sub',
2105 attrs => [],
2106 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2107 );
2108 if($mesg->{'resultCode'} == 0 &&
2109 $mesg->count != 0) {
2110 # Walk through all possible FAI container ou's
2111 my @sql_list;
2112 my $timestamp= &get_time();
2113 foreach my $ou (@{$mesg->{entries}}) {
2114 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2115 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2116 my @tmp_array=get_fai_release_entries($tmp_classes);
2117 if(@tmp_array) {
2118 foreach my $entry (@tmp_array) {
2119 if(defined($entry) && ref($entry) eq 'HASH') {
2120 my $sql=
2121 "INSERT INTO $table_name "
2122 ."(timestamp, release, class, type, state) VALUES ("
2123 .$timestamp.","
2124 ."'".$entry->{'release'}."',"
2125 ."'".$entry->{'class'}."',"
2126 ."'".$entry->{'type'}."',"
2127 ."'".$entry->{'state'}."')";
2128 push @sql_list, $sql;
2129 }
2130 }
2131 }
2132 }
2133 }
2135 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2136 if(@sql_list) {
2137 unshift @sql_list, "VACUUM";
2138 unshift @sql_list, "DELETE FROM $table_name";
2139 $fai_release_db->exec_statementlist(\@sql_list);
2140 }
2141 daemon_log("$session_id DEBUG: Done with inserting",7);
2142 }
2143 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2144 }
2145 $ldap_handle->disconnect;
2146 return $result;
2147 }
2149 sub get_fai_types {
2150 my $tmp_classes = shift || return undef;
2151 my @result;
2153 foreach my $type(keys %{$tmp_classes}) {
2154 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2155 my $entry = {
2156 type => $type,
2157 state => $tmp_classes->{$type}[0],
2158 };
2159 push @result, $entry;
2160 }
2161 }
2163 return @result;
2164 }
2166 sub get_fai_state {
2167 my $result = "";
2168 my $tmp_classes = shift || return $result;
2170 foreach my $type(keys %{$tmp_classes}) {
2171 if(defined($tmp_classes->{$type}[0])) {
2172 $result = $tmp_classes->{$type}[0];
2174 # State is equal for all types in class
2175 last;
2176 }
2177 }
2179 return $result;
2180 }
2182 sub resolve_fai_classes {
2183 my ($fai_base, $ldap_handle, $session_id) = @_;
2184 if (not defined $session_id) { $session_id = 0; }
2185 my $result;
2186 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2187 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2188 my $fai_classes;
2190 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2191 my $mesg= $ldap_handle->search(
2192 base => $fai_base,
2193 scope => 'sub',
2194 attrs => ['cn','objectClass','FAIstate'],
2195 filter => $fai_filter,
2196 );
2197 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2199 if($mesg->{'resultCode'} == 0 &&
2200 $mesg->count != 0) {
2201 foreach my $entry (@{$mesg->{entries}}) {
2202 if($entry->exists('cn')) {
2203 my $tmp_dn= $entry->dn();
2205 # Skip classname and ou dn parts for class
2206 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2208 # Skip classes without releases
2209 if((!defined($tmp_release)) || length($tmp_release)==0) {
2210 next;
2211 }
2213 my $tmp_cn= $entry->get_value('cn');
2214 my $tmp_state= $entry->get_value('FAIstate');
2216 my $tmp_type;
2217 # Get FAI type
2218 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2219 if(grep $_ eq $oclass, @possible_fai_classes) {
2220 $tmp_type= $oclass;
2221 last;
2222 }
2223 }
2225 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2226 # A Subrelease
2227 my @sub_releases = split(/,/, $tmp_release);
2229 # Walk through subreleases and build hash tree
2230 my $hash;
2231 while(my $tmp_sub_release = pop @sub_releases) {
2232 $hash .= "\{'$tmp_sub_release'\}->";
2233 }
2234 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2235 } else {
2236 # A branch, no subrelease
2237 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2238 }
2239 } elsif (!$entry->exists('cn')) {
2240 my $tmp_dn= $entry->dn();
2241 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2243 # Skip classes without releases
2244 if((!defined($tmp_release)) || length($tmp_release)==0) {
2245 next;
2246 }
2248 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2249 # A Subrelease
2250 my @sub_releases= split(/,/, $tmp_release);
2252 # Walk through subreleases and build hash tree
2253 my $hash;
2254 while(my $tmp_sub_release = pop @sub_releases) {
2255 $hash .= "\{'$tmp_sub_release'\}->";
2256 }
2257 # Remove the last two characters
2258 chop($hash);
2259 chop($hash);
2261 eval('$fai_classes->'.$hash.'= {}');
2262 } else {
2263 # A branch, no subrelease
2264 if(!exists($fai_classes->{$tmp_release})) {
2265 $fai_classes->{$tmp_release} = {};
2266 }
2267 }
2268 }
2269 }
2271 # The hash is complete, now we can honor the copy-on-write based missing entries
2272 foreach my $release (keys %$fai_classes) {
2273 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2274 }
2275 }
2276 return $result;
2277 }
2279 sub apply_fai_inheritance {
2280 my $fai_classes = shift || return {};
2281 my $tmp_classes;
2283 # Get the classes from the branch
2284 foreach my $class (keys %{$fai_classes}) {
2285 # Skip subreleases
2286 if($class =~ /^ou=.*$/) {
2287 next;
2288 } else {
2289 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2290 }
2291 }
2293 # Apply to each subrelease
2294 foreach my $subrelease (keys %{$fai_classes}) {
2295 if($subrelease =~ /ou=/) {
2296 foreach my $tmp_class (keys %{$tmp_classes}) {
2297 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2298 $fai_classes->{$subrelease}->{$tmp_class} =
2299 deep_copy($tmp_classes->{$tmp_class});
2300 } else {
2301 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2302 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2303 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2304 deep_copy($tmp_classes->{$tmp_class}->{$type});
2305 }
2306 }
2307 }
2308 }
2309 }
2310 }
2312 # Find subreleases in deeper levels
2313 foreach my $subrelease (keys %{$fai_classes}) {
2314 if($subrelease =~ /ou=/) {
2315 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2316 if($subsubrelease =~ /ou=/) {
2317 apply_fai_inheritance($fai_classes->{$subrelease});
2318 }
2319 }
2320 }
2321 }
2323 return $fai_classes;
2324 }
2326 sub get_fai_release_entries {
2327 my $tmp_classes = shift || return;
2328 my $parent = shift || "";
2329 my @result = shift || ();
2331 foreach my $entry (keys %{$tmp_classes}) {
2332 if(defined($entry)) {
2333 if($entry =~ /^ou=.*$/) {
2334 my $release_name = $entry;
2335 $release_name =~ s/ou=//g;
2336 if(length($parent)>0) {
2337 $release_name = $parent."/".$release_name;
2338 }
2339 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2340 foreach my $bufentry(@bufentries) {
2341 push @result, $bufentry;
2342 }
2343 } else {
2344 my @types = get_fai_types($tmp_classes->{$entry});
2345 foreach my $type (@types) {
2346 push @result,
2347 {
2348 'class' => $entry,
2349 'type' => $type->{'type'},
2350 'release' => $parent,
2351 'state' => $type->{'state'},
2352 };
2353 }
2354 }
2355 }
2356 }
2358 return @result;
2359 }
2361 sub deep_copy {
2362 my $this = shift;
2363 if (not ref $this) {
2364 $this;
2365 } elsif (ref $this eq "ARRAY") {
2366 [map deep_copy($_), @$this];
2367 } elsif (ref $this eq "HASH") {
2368 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2369 } else { die "what type is $_?" }
2370 }
2373 sub session_run_result {
2374 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2375 $kernel->sig(CHLD => "child_reap");
2376 }
2378 sub session_run_debug {
2379 my $result = $_[ARG0];
2380 print STDERR "$result\n";
2381 }
2383 sub session_run_done {
2384 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2385 delete $heap->{task}->{$task_id};
2386 }
2389 sub create_sources_list {
2390 my $session_id = shift;
2391 my $ldap_handle = &main::get_ldap_handle;
2392 my $result="/tmp/gosa_si_tmp_sources_list";
2394 # Remove old file
2395 if(stat($result)) {
2396 unlink($result);
2397 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2398 }
2400 my $fh;
2401 open($fh, ">$result");
2402 if (not defined $fh) {
2403 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2404 return undef;
2405 }
2406 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2407 my $mesg=$ldap_handle->search(
2408 base => $main::ldap_server_dn,
2409 scope => 'base',
2410 attrs => 'FAIrepository',
2411 filter => 'objectClass=FAIrepositoryServer'
2412 );
2413 if($mesg->count) {
2414 foreach my $entry(@{$mesg->{'entries'}}) {
2415 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2416 my ($server, $tag, $release, $sections)= split /\|/, $value;
2417 my $line = "deb $server $release";
2418 $sections =~ s/,/ /g;
2419 $line.= " $sections";
2420 print $fh $line."\n";
2421 }
2422 }
2423 }
2424 } else {
2425 if (defined $main::ldap_server_dn){
2426 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2427 } else {
2428 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2429 }
2430 }
2431 close($fh);
2433 return $result;
2434 }
2437 sub run_create_packages_list_db {
2438 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2439 my $session_id = $session->ID;
2441 my $task = POE::Wheel::Run->new(
2442 Priority => +20,
2443 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2444 StdoutEvent => "session_run_result",
2445 StderrEvent => "session_run_debug",
2446 CloseEvent => "session_run_done",
2447 );
2448 $heap->{task}->{ $task->ID } = $task;
2449 }
2452 sub create_packages_list_db {
2453 my ($ldap_handle, $sources_file, $session_id) = @_;
2455 # it should not be possible to trigger a recreation of packages_list_db
2456 # while packages_list_db is under construction, so set flag packages_list_under_construction
2457 # which is tested befor recreation can be started
2458 if (-r $packages_list_under_construction) {
2459 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2460 return;
2461 } else {
2462 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2463 # set packages_list_under_construction to true
2464 system("touch $packages_list_under_construction");
2465 @packages_list_statements=();
2466 }
2468 if (not defined $session_id) { $session_id = 0; }
2469 if (not defined $ldap_handle) {
2470 $ldap_handle= &get_ldap_handle();
2472 if (not defined $ldap_handle) {
2473 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2474 unlink($packages_list_under_construction);
2475 return;
2476 }
2477 }
2478 if (not defined $sources_file) {
2479 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2480 $sources_file = &create_sources_list($session_id);
2481 }
2483 if (not defined $sources_file) {
2484 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2485 unlink($packages_list_under_construction);
2486 return;
2487 }
2489 my $line;
2491 open(CONFIG, "<$sources_file") or do {
2492 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2493 unlink($packages_list_under_construction);
2494 return;
2495 };
2497 # Read lines
2498 while ($line = <CONFIG>){
2499 # Unify
2500 chop($line);
2501 $line =~ s/^\s+//;
2502 $line =~ s/^\s+/ /;
2504 # Strip comments
2505 $line =~ s/#.*$//g;
2507 # Skip empty lines
2508 if ($line =~ /^\s*$/){
2509 next;
2510 }
2512 # Interpret deb line
2513 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2514 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2515 my $section;
2516 foreach $section (split(' ', $sections)){
2517 &parse_package_info( $baseurl, $dist, $section, $session_id );
2518 }
2519 }
2520 }
2522 close (CONFIG);
2524 find(\&cleanup_and_extract, keys( %repo_dirs ));
2525 &main::strip_packages_list_statements();
2526 unshift @packages_list_statements, "VACUUM";
2527 $packages_list_db->exec_statementlist(\@packages_list_statements);
2528 unlink($packages_list_under_construction);
2529 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2530 return;
2531 }
2533 # This function should do some intensive task to minimize the db-traffic
2534 sub strip_packages_list_statements {
2535 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2536 my @new_statement_list=();
2537 my $hash;
2538 my $insert_hash;
2539 my $update_hash;
2540 my $delete_hash;
2541 my $local_timestamp=get_time();
2543 foreach my $existing_entry (@existing_entries) {
2544 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2545 }
2547 foreach my $statement (@packages_list_statements) {
2548 if($statement =~ /^INSERT/i) {
2549 # Assign the values from the insert statement
2550 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2551 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2552 if(exists($hash->{$distribution}->{$package}->{$version})) {
2553 # If section or description has changed, update the DB
2554 if(
2555 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2556 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2557 ) {
2558 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2559 }
2560 } else {
2561 # Insert a non-existing entry to db
2562 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2563 }
2564 } elsif ($statement =~ /^UPDATE/i) {
2565 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2566 /^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;
2567 foreach my $distribution (keys %{$hash}) {
2568 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2569 # update the insertion hash to execute only one query per package (insert instead insert+update)
2570 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2571 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2572 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2573 my $section;
2574 my $description;
2575 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2576 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2577 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2578 }
2579 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2580 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2581 }
2582 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2583 }
2584 }
2585 }
2586 }
2587 }
2589 # TODO: Check for orphaned entries
2591 # unroll the insert_hash
2592 foreach my $distribution (keys %{$insert_hash}) {
2593 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2594 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2595 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2596 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2597 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2598 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2599 ."'$local_timestamp')";
2600 }
2601 }
2602 }
2604 # unroll the update hash
2605 foreach my $distribution (keys %{$update_hash}) {
2606 foreach my $package (keys %{$update_hash->{$distribution}}) {
2607 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2608 my $set = "";
2609 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2610 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2611 }
2612 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2613 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2614 }
2615 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2616 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2617 }
2618 if(defined($set) and length($set) > 0) {
2619 $set .= "timestamp = '$local_timestamp'";
2620 } else {
2621 next;
2622 }
2623 push @new_statement_list,
2624 "UPDATE $main::packages_list_tn SET $set WHERE"
2625 ." distribution = '$distribution'"
2626 ." AND package = '$package'"
2627 ." AND version = '$version'";
2628 }
2629 }
2630 }
2632 @packages_list_statements = @new_statement_list;
2633 }
2636 sub parse_package_info {
2637 my ($baseurl, $dist, $section, $session_id)= @_;
2638 my ($package);
2639 if (not defined $session_id) { $session_id = 0; }
2640 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2641 $repo_dirs{ "${repo_path}/pool" } = 1;
2643 foreach $package ("Packages.gz"){
2644 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2645 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2646 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2647 }
2649 }
2652 sub get_package {
2653 my ($url, $dest, $session_id)= @_;
2654 if (not defined $session_id) { $session_id = 0; }
2656 my $tpath = dirname($dest);
2657 -d "$tpath" || mkpath "$tpath";
2659 # This is ugly, but I've no time to take a look at "how it works in perl"
2660 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2661 system("gunzip -cd '$dest' > '$dest.in'");
2662 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2663 unlink($dest);
2664 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2665 } else {
2666 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2667 }
2668 return 0;
2669 }
2672 sub parse_package {
2673 my ($path, $dist, $srv_path, $session_id)= @_;
2674 if (not defined $session_id) { $session_id = 0;}
2675 my ($package, $version, $section, $description);
2676 my $PACKAGES;
2677 my $timestamp = &get_time();
2679 if(not stat("$path.in")) {
2680 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2681 return;
2682 }
2684 open($PACKAGES, "<$path.in");
2685 if(not defined($PACKAGES)) {
2686 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2687 return;
2688 }
2690 # Read lines
2691 while (<$PACKAGES>){
2692 my $line = $_;
2693 # Unify
2694 chop($line);
2696 # Use empty lines as a trigger
2697 if ($line =~ /^\s*$/){
2698 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2699 push(@packages_list_statements, $sql);
2700 $package = "none";
2701 $version = "none";
2702 $section = "none";
2703 $description = "none";
2704 next;
2705 }
2707 # Trigger for package name
2708 if ($line =~ /^Package:\s/){
2709 ($package)= ($line =~ /^Package: (.*)$/);
2710 next;
2711 }
2713 # Trigger for version
2714 if ($line =~ /^Version:\s/){
2715 ($version)= ($line =~ /^Version: (.*)$/);
2716 next;
2717 }
2719 # Trigger for description
2720 if ($line =~ /^Description:\s/){
2721 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2722 next;
2723 }
2725 # Trigger for section
2726 if ($line =~ /^Section:\s/){
2727 ($section)= ($line =~ /^Section: (.*)$/);
2728 next;
2729 }
2731 # Trigger for filename
2732 if ($line =~ /^Filename:\s/){
2733 my ($filename) = ($line =~ /^Filename: (.*)$/);
2734 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2735 next;
2736 }
2737 }
2739 close( $PACKAGES );
2740 unlink( "$path.in" );
2741 }
2744 sub store_fileinfo {
2745 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2747 my %fileinfo = (
2748 'package' => $package,
2749 'dist' => $dist,
2750 'version' => $vers,
2751 );
2753 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2754 }
2757 sub cleanup_and_extract {
2758 my $fileinfo = $repo_files{ $File::Find::name };
2760 if( defined $fileinfo ) {
2762 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2763 my $sql;
2764 my $package = $fileinfo->{ 'package' };
2765 my $newver = $fileinfo->{ 'version' };
2767 mkpath($dir);
2768 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2770 if( -f "$dir/DEBIAN/templates" ) {
2772 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2774 my $tmpl= "";
2775 {
2776 local $/=undef;
2777 open FILE, "$dir/DEBIAN/templates";
2778 $tmpl = &encode_base64(<FILE>);
2779 close FILE;
2780 }
2781 rmtree("$dir/DEBIAN/templates");
2783 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2784 push @packages_list_statements, $sql;
2785 }
2786 }
2788 return;
2789 }
2792 sub register_at_foreign_servers {
2793 my ($kernel) = $_[KERNEL];
2795 # hole alle bekannten server aus known_server_db
2796 my $server_sql = "SELECT * FROM $known_server_tn";
2797 my $server_res = $known_server_db->exec_statement($server_sql);
2799 # no entries in known_server_db
2800 if (not ref(@$server_res[0]) eq "ARRAY") {
2801 # TODO
2802 }
2804 # detect already connected clients
2805 my $client_sql = "SELECT * FROM $known_clients_tn";
2806 my $client_res = $known_clients_db->exec_statement($client_sql);
2808 # send my server details to all other gosa-si-server within the network
2809 foreach my $hit (@$server_res) {
2810 my $hostname = @$hit[0];
2811 my $hostkey = &create_passwd;
2813 # add already connected clients to registration message
2814 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2815 &add_content2xml_hash($myhash, 'key', $hostkey);
2816 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2818 # build registration message and send it
2819 my $foreign_server_msg = &create_xml_string($myhash);
2820 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2821 }
2823 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2824 return;
2825 }
2828 #==== MAIN = main ==============================================================
2829 # parse commandline options
2830 Getopt::Long::Configure( "bundling" );
2831 GetOptions("h|help" => \&usage,
2832 "c|config=s" => \$cfg_file,
2833 "f|foreground" => \$foreground,
2834 "v|verbose+" => \$verbose,
2835 "no-arp+" => \$no_arp,
2836 );
2838 # read and set config parameters
2839 &check_cmdline_param ;
2840 &read_configfile;
2841 &check_pid;
2843 $SIG{CHLD} = 'IGNORE';
2845 # forward error messages to logfile
2846 if( ! $foreground ) {
2847 open( STDIN, '+>/dev/null' );
2848 open( STDOUT, '+>&STDIN' );
2849 open( STDERR, '+>&STDIN' );
2850 }
2852 # Just fork, if we are not in foreground mode
2853 if( ! $foreground ) {
2854 chdir '/' or die "Can't chdir to /: $!";
2855 $pid = fork;
2856 setsid or die "Can't start a new session: $!";
2857 umask 0;
2858 } else {
2859 $pid = $$;
2860 }
2862 # Do something useful - put our PID into the pid_file
2863 if( 0 != $pid ) {
2864 open( LOCK_FILE, ">$pid_file" );
2865 print LOCK_FILE "$pid\n";
2866 close( LOCK_FILE );
2867 if( !$foreground ) {
2868 exit( 0 )
2869 };
2870 }
2872 # parse head url and revision from svn
2873 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2874 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2875 $server_headURL = defined $1 ? $1 : 'unknown' ;
2876 $server_revision = defined $2 ? $2 : 'unknown' ;
2877 if ($server_headURL =~ /\/tag\// ||
2878 $server_headURL =~ /\/branches\// ) {
2879 $server_status = "stable";
2880 } else {
2881 $server_status = "developmental" ;
2882 }
2885 daemon_log(" ", 1);
2886 daemon_log("$0 started!", 1);
2887 daemon_log("status: $server_status", 1);
2888 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2890 # connect to incoming_db
2891 unlink($incoming_file_name);
2892 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2893 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2895 # connect to gosa-si job queue
2896 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2897 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2899 # connect to known_clients_db
2900 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2901 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2903 # connect to foreign_clients_db
2904 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2905 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2907 # connect to known_server_db
2908 unlink($known_server_file_name);
2909 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2910 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2912 # connect to login_usr_db
2913 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2914 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2916 # connect to fai_server_db and fai_release_db
2917 unlink($fai_server_file_name);
2918 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2919 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2921 unlink($fai_release_file_name);
2922 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2923 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2925 # connect to packages_list_db
2926 #unlink($packages_list_file_name);
2927 unlink($packages_list_under_construction);
2928 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2929 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2931 # connect to messaging_db
2932 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2933 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2936 # create xml object used for en/decrypting
2937 $xml = new XML::Simple();
2940 # foreign servers
2941 my @foreign_server_list;
2943 # add foreign server from cfg file
2944 if ($foreign_server_string ne "") {
2945 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2946 foreach my $foreign_server (@cfg_foreign_server_list) {
2947 push(@foreign_server_list, $foreign_server);
2948 }
2949 }
2951 # add foreign server from dns
2952 my @tmp_servers;
2953 if ( !$server_domain) {
2954 # Try our DNS Searchlist
2955 for my $domain(get_dns_domains()) {
2956 chomp($domain);
2957 my @tmp_domains= &get_server_addresses($domain);
2958 if(@tmp_domains) {
2959 for my $tmp_server(@tmp_domains) {
2960 push @tmp_servers, $tmp_server;
2961 }
2962 }
2963 }
2964 if(@tmp_servers && length(@tmp_servers)==0) {
2965 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2966 }
2967 } else {
2968 @tmp_servers = &get_server_addresses($server_domain);
2969 if( 0 == @tmp_servers ) {
2970 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2971 }
2972 }
2973 foreach my $server (@tmp_servers) {
2974 unshift(@foreign_server_list, $server);
2975 }
2976 # eliminate duplicate entries
2977 @foreign_server_list = &del_doubles(@foreign_server_list);
2978 my $all_foreign_server = join(", ", @foreign_server_list);
2979 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2981 # add all found foreign servers to known_server
2982 my $act_timestamp = &get_time();
2983 foreach my $foreign_server (@foreign_server_list) {
2985 #######################################
2986 # TODO for jan
2987 # do not add myself to known_server_db
2988 # work around!!!
2989 if ($foreign_server eq '172.16.2.89:20081') { next; }
2990 ######################################
2992 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2993 primkey=>['hostname'],
2994 hostname=>$foreign_server,
2995 status=>'not_jet_registered',
2996 hostkey=>"none",
2997 timestamp=>$act_timestamp,
2998 } );
2999 }
3002 POE::Component::Server::TCP->new(
3003 Alias => "TCP_SERVER",
3004 Port => $server_port,
3005 ClientInput => sub {
3006 my ($kernel, $input) = @_[KERNEL, ARG0];
3007 push(@tasks, $input);
3008 push(@msgs_to_decrypt, $input);
3009 $kernel->yield("msg_to_decrypt");
3010 },
3011 InlineStates => {
3012 msg_to_decrypt => \&msg_to_decrypt,
3013 next_task => \&next_task,
3014 task_result => \&handle_task_result,
3015 task_done => \&handle_task_done,
3016 task_debug => \&handle_task_debug,
3017 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3018 }
3019 );
3021 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3023 # create session for repeatedly checking the job queue for jobs
3024 POE::Session->create(
3025 inline_states => {
3026 _start => \&session_start,
3027 register_at_foreign_servers => \®ister_at_foreign_servers,
3028 sig_handler => \&sig_handler,
3029 next_task => \&next_task,
3030 task_result => \&handle_task_result,
3031 task_done => \&handle_task_done,
3032 task_debug => \&handle_task_debug,
3033 watch_for_next_tasks => \&watch_for_next_tasks,
3034 watch_for_new_messages => \&watch_for_new_messages,
3035 watch_for_delivery_messages => \&watch_for_delivery_messages,
3036 watch_for_done_messages => \&watch_for_done_messages,
3037 watch_for_new_jobs => \&watch_for_new_jobs,
3038 watch_for_done_jobs => \&watch_for_done_jobs,
3039 watch_for_old_known_clients => \&watch_for_old_known_clients,
3040 create_packages_list_db => \&run_create_packages_list_db,
3041 create_fai_server_db => \&run_create_fai_server_db,
3042 create_fai_release_db => \&run_create_fai_release_db,
3043 recreate_packages_db => \&run_recreate_packages_db,
3044 session_run_result => \&session_run_result,
3045 session_run_debug => \&session_run_debug,
3046 session_run_done => \&session_run_done,
3047 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3048 }
3049 );
3052 # import all modules
3053 &import_modules;
3055 # TODO
3056 # check wether all modules are gosa-si valid passwd check
3060 POE::Kernel->run();
3061 exit;