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 ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file,
86 $server_ip, $server_port, $ClientPackages_key,
87 $arp_activ, $gosa_unit_tag,
88 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_bus = 0;
115 $bus_activ = "true";
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-sd as well as the gosa-sd-bus
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 "bus" => {
215 "activ" => [\$bus_activ, "true"],
216 },
217 "server" => {
218 "port" => [\$server_port, "20081"],
219 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
229 "repo-path" => [\$repo_path, '/srv/www/repository'],
230 "ldap-uri" => [\$ldap_uri, ""],
231 "ldap-base" => [\$ldap_base, ""],
232 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
233 "ldap-admin-password" => [\$ldap_admin_password, ""],
234 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
235 "max-clients" => [\$max_clients, 10],
236 },
237 "GOsaPackages" => {
238 "ip" => [\$gosa_ip, "0.0.0.0"],
239 "port" => [\$gosa_port, "20082"],
240 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
241 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
242 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
243 "key" => [\$GosaPackages_key, "none"],
244 },
245 "ClientPackages" => {
246 "key" => [\$ClientPackages_key, "none"],
247 },
248 "ServerPackages"=> {
249 "address" => [\$foreign_server_string, ""],
250 "domain" => [\$server_domain, ""],
251 "key" => [\$ServerPackages_key, "none"],
252 "key-lifetime" => [\$foreign_servers_register_delay, 120],
253 }
254 );
257 #=== FUNCTION ================================================================
258 # NAME: usage
259 # PARAMETERS: nothing
260 # RETURNS: nothing
261 # DESCRIPTION: print out usage text to STDERR
262 #===============================================================================
263 sub usage {
264 print STDERR << "EOF" ;
265 usage: $prg [-hvf] [-c config]
267 -h : this (help) message
268 -c <file> : config file
269 -f : foreground, process will not be forked to background
270 -v : be verbose (multiple to increase verbosity)
271 -no-bus : starts $prg without connection to bus
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 chomp($msg);
325 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
326 if($level <= $verbose){
327 my ($seconds, $minutes, $hours, $monthday, $month,
328 $year, $weekday, $yearday, $sommertime) = localtime(time);
329 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
330 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
331 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
332 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
333 $month = $monthnames[$month];
334 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
335 $year+=1900;
336 my $name = $prg;
338 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
339 print LOG_HANDLE $log_msg;
340 if( $foreground ) {
341 print STDERR $log_msg;
342 }
343 }
344 close( LOG_HANDLE );
345 }
346 }
349 #=== FUNCTION ================================================================
350 # NAME: check_cmdline_param
351 # PARAMETERS: nothing
352 # RETURNS: nothing
353 # DESCRIPTION: validates commandline parameter
354 #===============================================================================
355 sub check_cmdline_param () {
356 my $err_config;
357 my $err_counter = 0;
358 if(not defined($cfg_file)) {
359 $cfg_file = "/etc/gosa-si/server.conf";
360 if(! -r $cfg_file) {
361 $err_config = "please specify a config file";
362 $err_counter += 1;
363 }
364 }
365 if( $err_counter > 0 ) {
366 &usage( "", 1 );
367 if( defined( $err_config)) { print STDERR "$err_config\n"}
368 print STDERR "\n";
369 exit( -1 );
370 }
371 }
374 #=== FUNCTION ================================================================
375 # NAME: check_pid
376 # PARAMETERS: nothing
377 # RETURNS: nothing
378 # DESCRIPTION: handels pid processing
379 #===============================================================================
380 sub check_pid {
381 $pid = -1;
382 # Check, if we are already running
383 if( open(LOCK_FILE, "<$pid_file") ) {
384 $pid = <LOCK_FILE>;
385 if( defined $pid ) {
386 chomp( $pid );
387 if( -f "/proc/$pid/stat" ) {
388 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
389 if( $stat ) {
390 daemon_log("ERROR: Already running",1);
391 close( LOCK_FILE );
392 exit -1;
393 }
394 }
395 }
396 close( LOCK_FILE );
397 unlink( $pid_file );
398 }
400 # create a syslog msg if it is not to possible to open PID file
401 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
402 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
403 if (open(LOCK_FILE, '<', $pid_file)
404 && ($pid = <LOCK_FILE>))
405 {
406 chomp($pid);
407 $msg .= "(PID $pid)\n";
408 } else {
409 $msg .= "(unable to read PID)\n";
410 }
411 if( ! ($foreground) ) {
412 openlog( $0, "cons,pid", "daemon" );
413 syslog( "warning", $msg );
414 closelog();
415 }
416 else {
417 print( STDERR " $msg " );
418 }
419 exit( -1 );
420 }
421 }
423 #=== FUNCTION ================================================================
424 # NAME: import_modules
425 # PARAMETERS: module_path - string - abs. path to the directory the modules
426 # are stored
427 # RETURNS: nothing
428 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
429 # state is on is imported by "require 'file';"
430 #===============================================================================
431 sub import_modules {
432 daemon_log(" ", 1);
434 if (not -e $modules_path) {
435 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
436 }
438 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
439 while (defined (my $file = readdir (DIR))) {
440 if (not $file =~ /(\S*?).pm$/) {
441 next;
442 }
443 my $mod_name = $1;
445 if( $file =~ /ArpHandler.pm/ ) {
446 if( $no_arp > 0 ) {
447 next;
448 }
449 }
451 eval { require $file; };
452 if ($@) {
453 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
454 daemon_log("$@", 5);
455 } else {
456 my $info = eval($mod_name.'::get_module_info()');
457 # Only load module if get_module_info() returns a non-null object
458 if( $info ) {
459 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
460 $known_modules->{$mod_name} = $info;
461 daemon_log("0 INFO: module $mod_name loaded", 5);
462 }
463 }
464 }
465 close (DIR);
466 }
469 #=== FUNCTION ================================================================
470 # NAME: sig_int_handler
471 # PARAMETERS: signal - string - signal arose from system
472 # RETURNS: noting
473 # DESCRIPTION: handels tasks to be done befor signal becomes active
474 #===============================================================================
475 sub sig_int_handler {
476 my ($signal) = @_;
478 # if (defined($ldap_handle)) {
479 # $ldap_handle->disconnect;
480 # }
481 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
484 daemon_log("shutting down gosa-si-server", 1);
485 system("kill `ps -C gosa-si-server-nobus -o pid=`");
486 }
487 $SIG{INT} = \&sig_int_handler;
490 sub check_key_and_xml_validity {
491 my ($crypted_msg, $module_key, $session_id) = @_;
492 my $msg;
493 my $msg_hash;
494 my $error_string;
495 eval{
496 $msg = &decrypt_msg($crypted_msg, $module_key);
498 if ($msg =~ /<xml>/i){
499 $msg =~ s/\s+/ /g; # just for better daemon_log
500 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
501 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
503 ##############
504 # check header
505 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
506 my $header_l = $msg_hash->{'header'};
507 if( 1 > @{$header_l} ) { die 'empty header tag'; }
508 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
509 my $header = @{$header_l}[0];
510 if( 0 == length $header) { die 'empty string in header tag'; }
512 ##############
513 # check source
514 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
515 my $source_l = $msg_hash->{'source'};
516 if( 1 > @{$source_l} ) { die 'empty source tag'; }
517 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
518 my $source = @{$source_l}[0];
519 if( 0 == length $source) { die 'source error'; }
521 ##############
522 # check target
523 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
524 my $target_l = $msg_hash->{'target'};
525 if( 1 > @{$target_l} ) { die 'empty target tag'; }
526 }
527 };
528 if($@) {
529 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
530 $msg = undef;
531 $msg_hash = undef;
532 }
534 return ($msg, $msg_hash);
535 }
538 sub check_outgoing_xml_validity {
539 my ($msg) = @_;
541 my $msg_hash;
542 eval{
543 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
545 ##############
546 # check header
547 my $header_l = $msg_hash->{'header'};
548 if( 1 != @{$header_l} ) {
549 die 'no or more than one headers specified';
550 }
551 my $header = @{$header_l}[0];
552 if( 0 == length $header) {
553 die 'header has length 0';
554 }
556 ##############
557 # check source
558 my $source_l = $msg_hash->{'source'};
559 if( 1 != @{$source_l} ) {
560 die 'no or more than 1 sources specified';
561 }
562 my $source = @{$source_l}[0];
563 if( 0 == length $source) {
564 die 'source has length 0';
565 }
566 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
567 $source =~ /^GOSA$/i ) {
568 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
569 }
571 ##############
572 # check target
573 my $target_l = $msg_hash->{'target'};
574 if( 0 == @{$target_l} ) {
575 die "no targets specified";
576 }
577 foreach my $target (@$target_l) {
578 if( 0 == length $target) {
579 die "target has length 0";
580 }
581 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582 $target =~ /^GOSA$/i ||
583 $target =~ /^\*$/ ||
584 $target =~ /KNOWN_SERVER/i ||
585 $target =~ /JOBDB/i ||
586 $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 ){
587 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
588 }
589 }
590 };
591 if($@) {
592 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
593 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
594 $msg_hash = undef;
595 }
597 return ($msg_hash);
598 }
601 sub input_from_known_server {
602 my ($input, $remote_ip, $session_id) = @_ ;
603 my ($msg, $msg_hash, $module);
605 my $sql_statement= "SELECT * FROM known_server";
606 my $query_res = $known_server_db->select_dbentry( $sql_statement );
608 while( my ($hit_num, $hit) = each %{ $query_res } ) {
609 my $host_name = $hit->{hostname};
610 if( not $host_name =~ "^$remote_ip") {
611 next;
612 }
613 my $host_key = $hit->{hostkey};
614 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
615 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
617 # check if module can open msg envelope with module key
618 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
619 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
620 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
621 daemon_log("$@", 8);
622 next;
623 }
624 else {
625 $msg = $tmp_msg;
626 $msg_hash = $tmp_msg_hash;
627 $module = "ServerPackages";
628 last;
629 }
630 }
632 if( (!$msg) || (!$msg_hash) || (!$module) ) {
633 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
634 }
636 return ($msg, $msg_hash, $module);
637 }
640 sub input_from_known_client {
641 my ($input, $remote_ip, $session_id) = @_ ;
642 my ($msg, $msg_hash, $module);
644 my $sql_statement= "SELECT * FROM known_clients";
645 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
646 while( my ($hit_num, $hit) = each %{ $query_res } ) {
647 my $host_name = $hit->{hostname};
648 if( not $host_name =~ /^$remote_ip:\d*$/) {
649 next;
650 }
651 my $host_key = $hit->{hostkey};
652 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
653 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
655 # check if module can open msg envelope with module key
656 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
658 if( (!$msg) || (!$msg_hash) ) {
659 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
660 &daemon_log("$@", 8);
661 next;
662 }
663 else {
664 $module = "ClientPackages";
665 last;
666 }
667 }
669 if( (!$msg) || (!$msg_hash) || (!$module) ) {
670 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
671 }
673 return ($msg, $msg_hash, $module);
674 }
677 sub input_from_unknown_host {
678 no strict "refs";
679 my ($input, $session_id) = @_ ;
680 my ($msg, $msg_hash, $module);
681 my $error_string;
683 my %act_modules = %$known_modules;
685 while( my ($mod, $info) = each(%act_modules)) {
687 # check a key exists for this module
688 my $module_key = ${$mod."_key"};
689 if( not defined $module_key ) {
690 if( $mod eq 'ArpHandler' ) {
691 next;
692 }
693 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
694 next;
695 }
696 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
698 # check if module can open msg envelope with module key
699 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
700 if( (not defined $msg) || (not defined $msg_hash) ) {
701 next;
702 }
703 else {
704 $module = $mod;
705 last;
706 }
707 }
709 if( (!$msg) || (!$msg_hash) || (!$module)) {
710 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
711 }
713 return ($msg, $msg_hash, $module);
714 }
717 sub create_ciphering {
718 my ($passwd) = @_;
719 if((!defined($passwd)) || length($passwd)==0) {
720 $passwd = "";
721 }
722 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
723 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
724 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
725 $my_cipher->set_iv($iv);
726 return $my_cipher;
727 }
730 sub encrypt_msg {
731 my ($msg, $key) = @_;
732 my $my_cipher = &create_ciphering($key);
733 my $len;
734 {
735 use bytes;
736 $len= 16-length($msg)%16;
737 }
738 $msg = "\0"x($len).$msg;
739 $msg = $my_cipher->encrypt($msg);
740 chomp($msg = &encode_base64($msg));
741 # there are no newlines allowed inside msg
742 $msg=~ s/\n//g;
743 return $msg;
744 }
747 sub decrypt_msg {
749 my ($msg, $key) = @_ ;
750 $msg = &decode_base64($msg);
751 my $my_cipher = &create_ciphering($key);
752 $msg = $my_cipher->decrypt($msg);
753 $msg =~ s/\0*//g;
754 return $msg;
755 }
758 sub get_encrypt_key {
759 my ($target) = @_ ;
760 my $encrypt_key;
761 my $error = 0;
763 # target can be in known_server
764 if( not defined $encrypt_key ) {
765 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
766 my $query_res = $known_server_db->select_dbentry( $sql_statement );
767 while( my ($hit_num, $hit) = each %{ $query_res } ) {
768 my $host_name = $hit->{hostname};
769 if( $host_name ne $target ) {
770 next;
771 }
772 $encrypt_key = $hit->{hostkey};
773 last;
774 }
775 }
777 # target can be in known_client
778 if( not defined $encrypt_key ) {
779 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
780 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
781 while( my ($hit_num, $hit) = each %{ $query_res } ) {
782 my $host_name = $hit->{hostname};
783 if( $host_name ne $target ) {
784 next;
785 }
786 $encrypt_key = $hit->{hostkey};
787 last;
788 }
789 }
791 return $encrypt_key;
792 }
795 #=== FUNCTION ================================================================
796 # NAME: open_socket
797 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
798 # [PeerPort] string necessary if port not appended by PeerAddr
799 # RETURNS: socket IO::Socket::INET
800 # DESCRIPTION: open a socket to PeerAddr
801 #===============================================================================
802 sub open_socket {
803 my ($PeerAddr, $PeerPort) = @_ ;
804 if(defined($PeerPort)){
805 $PeerAddr = $PeerAddr.":".$PeerPort;
806 }
807 my $socket;
808 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
809 Porto => "tcp",
810 Type => SOCK_STREAM,
811 Timeout => 5,
812 );
813 if(not defined $socket) {
814 return;
815 }
816 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
817 return $socket;
818 }
821 #=== FUNCTION ================================================================
822 # NAME: get_ip
823 # PARAMETERS: interface name (i.e. eth0)
824 # RETURNS: (ip address)
825 # DESCRIPTION: Uses ioctl to get ip address directly from system.
826 #===============================================================================
827 sub get_ip {
828 my $ifreq= shift;
829 my $result= "";
830 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
831 my $proto= getprotobyname('ip');
833 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
834 or die "socket: $!";
836 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
837 my ($if, $sin) = unpack 'a16 a16', $ifreq;
838 my ($port, $addr) = sockaddr_in $sin;
839 my $ip = inet_ntoa $addr;
841 if ($ip && length($ip) > 0) {
842 $result = $ip;
843 }
844 }
846 return $result;
847 }
850 sub get_local_ip_for_remote_ip {
851 my $remote_ip= shift;
852 my $result="0.0.0.0";
854 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855 if($remote_ip eq "127.0.0.1") {
856 $result = "127.0.0.1";
857 } else {
858 my $PROC_NET_ROUTE= ('/proc/net/route');
860 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861 or die "Could not open $PROC_NET_ROUTE";
863 my @ifs = <PROC_NET_ROUTE>;
865 close(PROC_NET_ROUTE);
867 # Eat header line
868 shift @ifs;
869 chomp @ifs;
870 foreach my $line(@ifs) {
871 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872 my $destination;
873 my $mask;
874 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879 # destination matches route, save mac and exit
880 $result= &get_ip($Iface);
881 last;
882 }
883 }
884 }
885 } else {
886 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
887 }
888 return $result;
889 }
892 sub send_msg_to_target {
893 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894 my $error = 0;
895 my $header;
896 my $timestamp = &get_time();
897 my $new_status;
898 my $act_status;
899 my ($sql_statement, $res);
901 if( $msg_header ) {
902 $header = "'$msg_header'-";
903 } else {
904 $header = "";
905 }
907 # Patch the source ip
908 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911 }
913 # encrypt xml msg
914 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916 # opensocket
917 my $socket = &open_socket($address);
918 if( !$socket ) {
919 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
920 $error++;
921 }
923 if( $error == 0 ) {
924 # send xml msg
925 print $socket $crypted_msg."\n";
927 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928 daemon_log("$session_id DEBUG: message:\n$msg", 9);
930 }
932 # close socket in any case
933 if( $socket ) {
934 close $socket;
935 }
937 if( $error > 0 ) { $new_status = "down"; }
938 else { $new_status = $msg_header; }
941 # known_clients
942 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
943 $res = $known_clients_db->select_dbentry($sql_statement);
944 if( keys(%$res) == 1) {
945 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
946 if ($act_status eq "down" && $new_status eq "down") {
947 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948 $res = $known_clients_db->del_dbentry($sql_statement);
949 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950 } else {
951 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952 $res = $known_clients_db->update_dbentry($sql_statement);
953 if($new_status eq "down"){
954 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955 } else {
956 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957 }
958 }
959 }
961 # known_server
962 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963 $res = $known_server_db->select_dbentry($sql_statement);
964 if( keys(%$res) == 1) {
965 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
966 if ($act_status eq "down" && $new_status eq "down") {
967 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968 $res = $known_server_db->del_dbentry($sql_statement);
969 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970 }
971 else {
972 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_server_db->update_dbentry($sql_statement);
974 if($new_status eq "down"){
975 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976 } else {
977 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978 }
979 }
980 }
981 return $error;
982 }
985 sub update_jobdb_status_for_send_msgs {
986 my ($answer, $error) = @_;
987 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
988 my $jobdb_id = $1;
990 # sending msg faild
991 if( $error ) {
992 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
993 my $sql_statement = "UPDATE $job_queue_tn ".
994 "SET status='error', result='can not deliver msg, please consult log file' ".
995 "WHERE id=$jobdb_id";
996 my $res = $job_db->update_dbentry($sql_statement);
997 }
999 # sending msg was successful
1000 } else {
1001 my $sql_statement = "UPDATE $job_queue_tn ".
1002 "SET status='done' ".
1003 "WHERE id=$jobdb_id AND status='processed'";
1004 my $res = $job_db->update_dbentry($sql_statement);
1005 }
1006 }
1007 }
1010 sub sig_handler {
1011 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1012 daemon_log("0 INFO got signal '$signal'", 1);
1013 $kernel->sig_handled();
1014 return;
1015 }
1018 sub msg_to_decrypt {
1019 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1020 my $session_id = $session->ID;
1021 my ($msg, $msg_hash, $module);
1022 my $error = 0;
1024 # hole neue msg aus @msgs_to_decrypt
1025 my $next_msg = shift @msgs_to_decrypt;
1027 # entschlüssle sie
1029 # msg is from a new client or gosa
1030 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1031 # msg is from a gosa-si-server or gosa-si-bus
1032 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1033 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1034 }
1035 # msg is from a gosa-si-client
1036 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1038 }
1039 # an error occurred
1040 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1042 # could not understand a msg from its server the client cause a re-registering process
1043 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1044 "' to cause a re-registering of the client if necessary", 5);
1045 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1047 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1048 my $host_name = $hit->{'hostname'};
1049 my $host_key = $hit->{'hostkey'};
1050 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053 }
1054 $error++;
1055 }
1057 my $header;
1058 my $target;
1059 my $done = 0;
1060 my $sql;
1061 my $res;
1062 # check whether this message should be processed here
1063 if ($error == 0) {
1064 $header = @{$msg_hash->{'header'}}[0];
1065 $target = @{$msg_hash->{'target'}}[0];
1067 # target is own address without forward_to_gosa-tag -> process here
1068 if (not $done) {
1069 if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1070 $done = 1;
1071 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1072 }
1073 }
1075 # target is a client address in known_clients -> process here
1076 if (not $done) {
1077 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1078 $res = $known_clients_db->select_dbentry($sql);
1079 if (keys(%$res) > 0) {
1080 $done = 1;
1081 my $hostname = $res->{1}->{'hostname'};
1082 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1083 print STDERR "target is a client address in known_clients -> process here\n";
1084 }
1085 }
1087 # if message should be processed here -> add message to incoming_db
1088 if ($done) {
1089 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1090 primkey=>[],
1091 headertag=>$header,
1092 targettag=>$target,
1093 xmlmessage=>$msg,
1094 timestamp=>&get_time,
1095 module=>$module,
1096 sessionid=>$session_id,
1097 } );
1099 }
1101 # target is own address with forward_to_gosa-tag -> forward to gosa
1102 if (not $done) {
1103 if (($target eq $server_address) && (exists $msg_hash->{'forward_to_gosa'})){
1104 if( $msg =~ s/session_id=(\d+)$// ) {
1105 my $session_id = $1 ;
1106 }
1107 if( defined $session_id ) {
1108 my $session_reference = $kernel->ID_id_to_session($session_id);
1109 if( defined $session_reference ) {
1110 $heap = $session_reference->get_heap();
1111 }
1112 }
1113 if(exists $heap->{'client'}) {
1114 $msg = &encrypt_msg($msg, $GosaPackages_key);
1115 $heap->{'client'}->put($msg);
1116 }
1117 $done = 1;
1118 print STDERR "target is own address with forward_to_gosa-tag -> forward to gosa\n";
1119 }
1121 }
1123 # target is a client address in foreign_clients -> forward to registration server
1124 if (not $done) {
1125 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1126 $res = $foreign_clients_db->select_dbentry($sql);
1127 if (keys(%$res) > 0) {
1128 my $hostname = $res->{1}->{'hostname'};
1129 my $regserver = $res->{1}->{'regserver'};
1130 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1131 my $res = $known_server_db->select_dbentry($sql);
1132 if (keys(%$res) > 0) {
1133 my $regserver_key = $res->{1}->{'hostkey'};
1134 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1135 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1136 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1137 }
1138 $done = 1;
1139 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1140 }
1141 }
1143 # target is a server address -> forward to server
1144 if (not $done) {
1145 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1146 $res = $known_server_db->select_dbentry($sql);
1147 if (keys(%$res) > 0) {
1148 my $hostkey = $res->{1}->{'hostkey'};
1149 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1150 $done = 1;
1151 print STDERR "target is a server address -> forward to server\n";
1152 }
1153 }
1156 }
1157 return;
1158 }
1161 sub next_task {
1162 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1163 my $running_task = POE::Wheel::Run->new(
1164 Program => sub { process_task($session, $heap, $task) },
1165 StdioFilter => POE::Filter::Reference->new(),
1166 StdoutEvent => "task_result",
1167 StderrEvent => "task_debug",
1168 CloseEvent => "task_done",
1169 );
1170 $heap->{task}->{ $running_task->ID } = $running_task;
1171 }
1173 sub handle_task_result {
1174 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1175 my $client_answer = $result->{'answer'};
1176 if( $client_answer =~ s/session_id=(\d+)$// ) {
1177 my $session_id = $1;
1178 if( defined $session_id ) {
1179 my $session_reference = $kernel->ID_id_to_session($session_id);
1180 if( defined $session_reference ) {
1181 $heap = $session_reference->get_heap();
1182 }
1183 }
1185 if(exists $heap->{'client'}) {
1186 $heap->{'client'}->put($client_answer);
1187 }
1188 }
1189 $kernel->sig(CHLD => "child_reap");
1190 }
1192 sub handle_task_debug {
1193 my $result = $_[ARG0];
1194 print STDERR "$result\n";
1195 }
1197 sub handle_task_done {
1198 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1199 delete $heap->{task}->{$task_id};
1200 }
1202 sub process_task {
1203 no strict "refs";
1204 my ($session, $heap, $task) = @_;
1205 my $error = 0;
1206 my $answer_l;
1207 my ($answer_header, @answer_target_l, $answer_source);
1208 my $client_answer = "";
1210 # prepare all variables needed to process message
1211 my $msg = $task->{'xmlmessage'};
1212 my $incoming_id = $task->{'id'};
1213 my $module = $task->{'module'};
1214 my $header = $task->{'headertag'};
1215 my $session_id = $task->{'sessionid'};
1216 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1217 my $source = @{$msg_hash->{'source'}}[0];
1219 # set timestamp of incoming client uptodate, so client will not
1220 # be deleted from known_clients because of expiration
1221 my $act_time = &get_time();
1222 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1223 my $res = $known_clients_db->exec_statement($sql);
1225 ######################
1226 # process incoming msg
1227 if( $error == 0) {
1228 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1229 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1230 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1232 if ( 0 < @{$answer_l} ) {
1233 my $answer_str = join("\n", @{$answer_l});
1234 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1235 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1236 }
1237 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1238 } else {
1239 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1240 }
1242 }
1243 if( !$answer_l ) { $error++ };
1245 ########
1246 # answer
1247 if( $error == 0 ) {
1249 foreach my $answer ( @{$answer_l} ) {
1250 # check outgoing msg to xml validity
1251 my $answer_hash = &check_outgoing_xml_validity($answer);
1252 if( not defined $answer_hash ) { next; }
1254 $answer_header = @{$answer_hash->{'header'}}[0];
1255 @answer_target_l = @{$answer_hash->{'target'}};
1256 $answer_source = @{$answer_hash->{'source'}}[0];
1258 # deliver msg to all targets
1259 foreach my $answer_target ( @answer_target_l ) {
1261 # targets of msg are all gosa-si-clients in known_clients_db
1262 if( $answer_target eq "*" ) {
1263 # answer is for all clients
1264 my $sql_statement= "SELECT * FROM known_clients";
1265 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1266 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1267 my $host_name = $hit->{hostname};
1268 my $host_key = $hit->{hostkey};
1269 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1270 &update_jobdb_status_for_send_msgs($answer, $error);
1271 }
1272 }
1274 # targets of msg are all gosa-si-server in known_server_db
1275 elsif( $answer_target eq "KNOWN_SERVER" ) {
1276 # answer is for all server in known_server
1277 my $sql_statement= "SELECT * FROM $known_server_tn";
1278 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1279 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1280 my $host_name = $hit->{hostname};
1281 my $host_key = $hit->{hostkey};
1282 $answer =~ s/<target>KNOWN_SERVER<\/target>/<target>$host_name<\/target>/g;
1283 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1284 &update_jobdb_status_for_send_msgs($answer, $error);
1285 }
1286 }
1288 # target of msg is GOsa
1289 elsif( $answer_target eq "GOSA" ) {
1290 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1291 my $add_on = "";
1292 if( defined $session_id ) {
1293 $add_on = ".session_id=$session_id";
1294 }
1295 # answer is for GOSA and has to returned to connected client
1296 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1297 $client_answer = $gosa_answer.$add_on;
1298 }
1300 # target of msg is job queue at this host
1301 elsif( $answer_target eq "JOBDB") {
1302 $answer =~ /<header>(\S+)<\/header>/;
1303 my $header;
1304 if( defined $1 ) { $header = $1; }
1305 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1306 &update_jobdb_status_for_send_msgs($answer, $error);
1307 }
1309 # target of msg is a mac address
1310 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 ) {
1311 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1312 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1313 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1314 my $found_ip_flag = 0;
1315 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1316 my $host_name = $hit->{hostname};
1317 my $host_key = $hit->{hostkey};
1318 $answer =~ s/$answer_target/$host_name/g;
1319 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1320 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1321 &update_jobdb_status_for_send_msgs($answer, $error);
1322 $found_ip_flag++ ;
1323 }
1324 if( $found_ip_flag == 0) {
1325 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1326 if( $bus_activ eq "true" ) {
1327 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1328 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1329 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1330 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1331 my $bus_address = $hit->{hostname};
1332 my $bus_key = $hit->{hostkey};
1333 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1334 &update_jobdb_status_for_send_msgs($answer, $error);
1335 last;
1336 }
1337 }
1339 }
1341 # answer is for one specific host
1342 } else {
1343 # get encrypt_key
1344 my $encrypt_key = &get_encrypt_key($answer_target);
1345 if( not defined $encrypt_key ) {
1346 # unknown target, forward msg to bus
1347 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1348 if( $bus_activ eq "true" ) {
1349 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1350 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1351 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1352 my $res_length = keys( %{$query_res} );
1353 if( $res_length == 0 ){
1354 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1355 "no bus found in known_server", 3);
1356 }
1357 else {
1358 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1359 my $bus_key = $hit->{hostkey};
1360 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1361 &update_jobdb_status_for_send_msgs($answer, $error);
1362 }
1363 }
1364 }
1365 next;
1366 }
1367 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1368 &update_jobdb_status_for_send_msgs($answer, $error);
1369 }
1370 }
1371 }
1372 }
1374 my $filter = POE::Filter::Reference->new();
1375 my %result = (
1376 status => "seems ok to me",
1377 answer => $client_answer,
1378 );
1380 my $output = $filter->put( [ \%result ] );
1381 print @$output;
1384 }
1386 sub session_start {
1387 my ($kernel) = $_[KERNEL];
1388 &trigger_db_loop($kernel);
1389 $global_kernel = $kernel;
1390 $kernel->yield('register_at_foreign_servers');
1391 $kernel->yield('create_fai_server_db', $fai_server_tn );
1392 $kernel->yield('create_fai_release_db', $fai_release_tn );
1393 $kernel->yield('watch_for_next_tasks');
1394 $kernel->sig(USR1 => "sig_handler");
1395 $kernel->sig(USR2 => "create_packages_list_db");
1396 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1397 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1398 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1399 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1400 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1401 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1403 }
1405 sub trigger_db_loop {
1406 my ($kernel) = @_ ;
1407 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1408 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1409 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1410 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1411 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1412 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1413 }
1416 sub watch_for_done_jobs {
1417 my ($kernel,$heap) = @_[KERNEL, HEAP];
1419 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1420 my $res = $job_db->select_dbentry( $sql_statement );
1422 while( my ($id, $hit) = each %{$res} ) {
1423 my $jobdb_id = $hit->{id};
1424 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1425 my $res = $job_db->del_dbentry($sql_statement);
1426 }
1428 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1429 }
1432 sub watch_for_new_jobs {
1433 if($watch_for_new_jobs_in_progress == 0) {
1434 $watch_for_new_jobs_in_progress = 1;
1435 my ($kernel,$heap) = @_[KERNEL, HEAP];
1437 # check gosa job queue for jobs with executable timestamp
1438 my $timestamp = &get_time();
1439 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1440 my $res = $job_db->exec_statement( $sql_statement );
1442 # Merge all new jobs that would do the same actions
1443 my @drops;
1444 my $hits;
1445 foreach my $hit (reverse @{$res} ) {
1446 my $macaddress= lc @{$hit}[8];
1447 my $headertag= @{$hit}[5];
1448 if(
1449 defined($hits->{$macaddress}) &&
1450 defined($hits->{$macaddress}->{$headertag}) &&
1451 defined($hits->{$macaddress}->{$headertag}[0])
1452 ) {
1453 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1454 }
1455 $hits->{$macaddress}->{$headertag}= $hit;
1456 }
1458 # Delete new jobs with a matching job in state 'processing'
1459 foreach my $macaddress (keys %{$hits}) {
1460 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1461 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1462 if(defined($jobdb_id)) {
1463 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1464 my $res = $job_db->exec_statement( $sql_statement );
1465 foreach my $hit (@{$res}) {
1466 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1467 }
1468 } else {
1469 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1470 }
1471 }
1472 }
1474 # Commit deletion
1475 $job_db->exec_statementlist(\@drops);
1477 # Look for new jobs that could be executed
1478 foreach my $macaddress (keys %{$hits}) {
1480 # Look if there is an executing job
1481 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1482 my $res = $job_db->exec_statement( $sql_statement );
1484 # Skip new jobs for host if there is a processing job
1485 if(defined($res) and defined @{$res}[0]) {
1486 next;
1487 }
1489 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1490 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1491 if(defined($jobdb_id)) {
1492 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1494 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1495 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1496 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1498 # expect macaddress is unique!!!!!!
1499 my $target = $res_hash->{1}->{hostname};
1501 # change header
1502 $job_msg =~ s/<header>job_/<header>gosa_/;
1504 # add sqlite_id
1505 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1507 $job_msg =~ /<header>(\S+)<\/header>/;
1508 my $header = $1 ;
1509 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1511 # update status in job queue to 'processing'
1512 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1513 my $res = $job_db->update_dbentry($sql_statement);
1514 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1516 # We don't want parallel processing
1517 last;
1518 }
1519 }
1520 }
1522 $watch_for_new_jobs_in_progress = 0;
1523 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1524 }
1525 }
1528 sub watch_for_new_messages {
1529 my ($kernel,$heap) = @_[KERNEL, HEAP];
1530 my @coll_user_msg; # collection list of outgoing messages
1532 # check messaging_db for new incoming messages with executable timestamp
1533 my $timestamp = &get_time();
1534 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1535 my $res = $messaging_db->exec_statement( $sql_statement );
1536 foreach my $hit (@{$res}) {
1538 # create outgoing messages
1539 my $message_to = @{$hit}[3];
1540 # translate message_to to plain login name
1541 my @message_to_l = split(/,/, $message_to);
1542 my %receiver_h;
1543 foreach my $receiver (@message_to_l) {
1544 if ($receiver =~ /^u_([\s\S]*)$/) {
1545 $receiver_h{$1} = 0;
1546 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1547 my $group_name = $1;
1548 # fetch all group members from ldap and add them to receiver hash
1549 my $ldap_handle = &get_ldap_handle();
1550 if (defined $ldap_handle) {
1551 my $mesg = $ldap_handle->search(
1552 base => $ldap_base,
1553 scope => 'sub',
1554 attrs => ['memberUid'],
1555 filter => "cn=$group_name",
1556 );
1557 if ($mesg->count) {
1558 my @entries = $mesg->entries;
1559 foreach my $entry (@entries) {
1560 my @receivers= $entry->get_value("memberUid");
1561 foreach my $receiver (@receivers) {
1562 $receiver_h{$1} = 0;
1563 }
1564 }
1565 }
1566 # translating errors ?
1567 if ($mesg->code) {
1568 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1569 }
1570 # ldap handle error ?
1571 } else {
1572 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1573 }
1574 } else {
1575 my $sbjct = &encode_base64(@{$hit}[1]);
1576 my $msg = &encode_base64(@{$hit}[7]);
1577 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1578 }
1579 }
1580 my @receiver_l = keys(%receiver_h);
1582 my $message_id = @{$hit}[0];
1584 #add each outgoing msg to messaging_db
1585 my $receiver;
1586 foreach $receiver (@receiver_l) {
1587 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1588 "VALUES ('".
1589 $message_id."', '". # id
1590 @{$hit}[1]."', '". # subject
1591 @{$hit}[2]."', '". # message_from
1592 $receiver."', '". # message_to
1593 "none"."', '". # flag
1594 "out"."', '". # direction
1595 @{$hit}[6]."', '". # delivery_time
1596 @{$hit}[7]."', '". # message
1597 $timestamp."'". # timestamp
1598 ")";
1599 &daemon_log("M DEBUG: $sql_statement", 1);
1600 my $res = $messaging_db->exec_statement($sql_statement);
1601 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1602 }
1604 # set incoming message to flag d=deliverd
1605 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1606 &daemon_log("M DEBUG: $sql_statement", 7);
1607 $res = $messaging_db->update_dbentry($sql_statement);
1608 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1609 }
1611 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1612 return;
1613 }
1615 sub watch_for_delivery_messages {
1616 my ($kernel, $heap) = @_[KERNEL, HEAP];
1618 # select outgoing messages
1619 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1620 #&daemon_log("0 DEBUG: $sql", 7);
1621 my $res = $messaging_db->exec_statement( $sql_statement );
1623 # build out msg for each usr
1624 foreach my $hit (@{$res}) {
1625 my $receiver = @{$hit}[3];
1626 my $msg_id = @{$hit}[0];
1627 my $subject = @{$hit}[1];
1628 my $message = @{$hit}[7];
1630 # resolve usr -> host where usr is logged in
1631 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1632 #&daemon_log("0 DEBUG: $sql", 7);
1633 my $res = $login_users_db->exec_statement($sql);
1635 # reciver is logged in nowhere
1636 if (not ref(@$res[0]) eq "ARRAY") { next; }
1638 my $send_succeed = 0;
1639 foreach my $hit (@$res) {
1640 my $receiver_host = @$hit[0];
1641 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1643 # fetch key to encrypt msg propperly for usr/host
1644 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1645 &daemon_log("0 DEBUG: $sql", 7);
1646 my $res = $known_clients_db->select_dbentry($sql);
1648 # host is already down
1649 if (not ref(@$res[0]) eq "ARRAY") { next; }
1651 # host is on
1652 my $receiver_key = @{@{$res}[0]}[2];
1653 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1654 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1655 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1656 if ($error == 0 ) {
1657 $send_succeed++ ;
1658 }
1659 }
1661 if ($send_succeed) {
1662 # set outgoing msg at db to deliverd
1663 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1664 &daemon_log("0 DEBUG: $sql", 7);
1665 my $res = $messaging_db->exec_statement($sql);
1666 }
1667 }
1669 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1670 return;
1671 }
1674 sub watch_for_done_messages {
1675 my ($kernel,$heap) = @_[KERNEL, HEAP];
1677 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1678 #&daemon_log("0 DEBUG: $sql", 7);
1679 my $res = $messaging_db->exec_statement($sql);
1681 foreach my $hit (@{$res}) {
1682 my $msg_id = @{$hit}[0];
1684 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1685 #&daemon_log("0 DEBUG: $sql", 7);
1686 my $res = $messaging_db->exec_statement($sql);
1688 # not all usr msgs have been seen till now
1689 if ( ref(@$res[0]) eq "ARRAY") { next; }
1691 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1692 #&daemon_log("0 DEBUG: $sql", 7);
1693 $res = $messaging_db->exec_statement($sql);
1695 }
1697 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1698 return;
1699 }
1702 sub watch_for_old_known_clients {
1703 my ($kernel,$heap) = @_[KERNEL, HEAP];
1705 my $sql_statement = "SELECT * FROM $known_clients_tn";
1706 my $res = $known_clients_db->select_dbentry( $sql_statement );
1708 my $act_time = int(&get_time());
1710 while ( my ($hit_num, $hit) = each %$res) {
1711 my $expired_timestamp = int($hit->{'timestamp'});
1712 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1713 my $dt = DateTime->new( year => $1,
1714 month => $2,
1715 day => $3,
1716 hour => $4,
1717 minute => $5,
1718 second => $6,
1719 );
1721 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1722 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1723 if ($act_time > $expired_timestamp) {
1724 my $hostname = $hit->{'hostname'};
1725 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1726 my $del_res = $known_clients_db->exec_statement($del_sql);
1728 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1729 }
1731 }
1733 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1734 }
1737 sub watch_for_next_tasks {
1738 my ($kernel,$heap) = @_[KERNEL, HEAP];
1740 my $sql = "SELECT * FROM $incoming_tn";
1741 my $res = $incoming_db->select_dbentry($sql);
1743 while ( my ($hit_num, $hit) = each %$res) {
1744 my $headertag = $hit->{'headertag'};
1745 if ($headertag =~ /^answer_(\d+)/) {
1746 # do not start processing, this message is for a still running POE::Wheel
1747 next;
1748 }
1749 my $message_id = $hit->{'id'};
1750 $kernel->yield('next_task', $hit);
1752 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1753 my $res = $incoming_db->exec_statement($sql);
1754 }
1756 $kernel->delay_set('watch_for_next_tasks', 1);
1757 }
1760 sub get_ldap_handle {
1761 my ($session_id) = @_;
1762 my $heap;
1763 my $ldap_handle;
1765 if (not defined $session_id ) { $session_id = 0 };
1766 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1768 if ($session_id == 0) {
1769 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1770 $ldap_handle = Net::LDAP->new( $ldap_uri );
1771 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password);
1773 } else {
1774 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1775 if( defined $session_reference ) {
1776 $heap = $session_reference->get_heap();
1777 }
1779 if (not defined $heap) {
1780 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1781 return;
1782 }
1784 # TODO: This "if" is nonsense, because it doesn't prove that the
1785 # used handle is still valid - or if we've to reconnect...
1786 #if (not exists $heap->{ldap_handle}) {
1787 $ldap_handle = Net::LDAP->new( $ldap_uri );
1788 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1789 $heap->{ldap_handle} = $ldap_handle;
1790 #}
1791 }
1792 return $ldap_handle;
1793 }
1796 sub change_fai_state {
1797 my ($st, $targets, $session_id) = @_;
1798 $session_id = 0 if not defined $session_id;
1799 # Set FAI state to localboot
1800 my %mapActions= (
1801 reboot => '',
1802 update => 'softupdate',
1803 localboot => 'localboot',
1804 reinstall => 'install',
1805 rescan => '',
1806 wake => '',
1807 memcheck => 'memcheck',
1808 sysinfo => 'sysinfo',
1809 install => 'install',
1810 );
1812 # Return if this is unknown
1813 if (!exists $mapActions{ $st }){
1814 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1815 return;
1816 }
1818 my $state= $mapActions{ $st };
1820 my $ldap_handle = &get_ldap_handle($session_id);
1821 if( defined($ldap_handle) ) {
1823 # Build search filter for hosts
1824 my $search= "(&(objectClass=GOhard)";
1825 foreach (@{$targets}){
1826 $search.= "(macAddress=$_)";
1827 }
1828 $search.= ")";
1830 # If there's any host inside of the search string, procress them
1831 if (!($search =~ /macAddress/)){
1832 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1833 return;
1834 }
1836 # Perform search for Unit Tag
1837 my $mesg = $ldap_handle->search(
1838 base => $ldap_base,
1839 scope => 'sub',
1840 attrs => ['dn', 'FAIstate', 'objectClass'],
1841 filter => "$search"
1842 );
1844 if ($mesg->count) {
1845 my @entries = $mesg->entries;
1846 foreach my $entry (@entries) {
1847 # Only modify entry if it is not set to '$state'
1848 if ($entry->get_value("FAIstate") ne "$state"){
1849 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1850 my $result;
1851 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1852 if (exists $tmp{'FAIobject'}){
1853 if ($state eq ''){
1854 $result= $ldap_handle->modify($entry->dn, changes => [
1855 delete => [ FAIstate => [] ] ]);
1856 } else {
1857 $result= $ldap_handle->modify($entry->dn, changes => [
1858 replace => [ FAIstate => $state ] ]);
1859 }
1860 } elsif ($state ne ''){
1861 $result= $ldap_handle->modify($entry->dn, changes => [
1862 add => [ objectClass => 'FAIobject' ],
1863 add => [ FAIstate => $state ] ]);
1864 }
1866 # Errors?
1867 if ($result->code){
1868 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1869 }
1870 } else {
1871 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1872 }
1873 }
1874 }
1875 # if no ldap handle defined
1876 } else {
1877 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1878 }
1880 }
1883 sub change_goto_state {
1884 my ($st, $targets, $session_id) = @_;
1885 $session_id = 0 if not defined $session_id;
1887 # Switch on or off?
1888 my $state= $st eq 'active' ? 'active': 'locked';
1890 my $ldap_handle = &get_ldap_handle($session_id);
1891 if( defined($ldap_handle) ) {
1893 # Build search filter for hosts
1894 my $search= "(&(objectClass=GOhard)";
1895 foreach (@{$targets}){
1896 $search.= "(macAddress=$_)";
1897 }
1898 $search.= ")";
1900 # If there's any host inside of the search string, procress them
1901 if (!($search =~ /macAddress/)){
1902 return;
1903 }
1905 # Perform search for Unit Tag
1906 my $mesg = $ldap_handle->search(
1907 base => $ldap_base,
1908 scope => 'sub',
1909 attrs => ['dn', 'gotoMode'],
1910 filter => "$search"
1911 );
1913 if ($mesg->count) {
1914 my @entries = $mesg->entries;
1915 foreach my $entry (@entries) {
1917 # Only modify entry if it is not set to '$state'
1918 if ($entry->get_value("gotoMode") ne $state){
1920 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1921 my $result;
1922 $result= $ldap_handle->modify($entry->dn, changes => [
1923 replace => [ gotoMode => $state ] ]);
1925 # Errors?
1926 if ($result->code){
1927 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1928 }
1930 }
1931 }
1932 }
1934 }
1935 }
1938 sub run_create_fai_server_db {
1939 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1940 my $session_id = $session->ID;
1941 my $task = POE::Wheel::Run->new(
1942 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1943 StdoutEvent => "session_run_result",
1944 StderrEvent => "session_run_debug",
1945 CloseEvent => "session_run_done",
1946 );
1948 $heap->{task}->{ $task->ID } = $task;
1949 return;
1950 }
1953 sub create_fai_server_db {
1954 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1955 my $result;
1957 if (not defined $session_id) { $session_id = 0; }
1958 my $ldap_handle = &get_ldap_handle();
1959 if(defined($ldap_handle)) {
1960 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1961 my $mesg= $ldap_handle->search(
1962 base => $ldap_base,
1963 scope => 'sub',
1964 attrs => ['FAIrepository', 'gosaUnitTag'],
1965 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1966 );
1967 if($mesg->{'resultCode'} == 0 &&
1968 $mesg->count != 0) {
1969 foreach my $entry (@{$mesg->{entries}}) {
1970 if($entry->exists('FAIrepository')) {
1971 # Add an entry for each Repository configured for server
1972 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1973 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1974 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1975 $result= $fai_server_db->add_dbentry( {
1976 table => $table_name,
1977 primkey => ['server', 'release', 'tag'],
1978 server => $tmp_url,
1979 release => $tmp_release,
1980 sections => $tmp_sections,
1981 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1982 } );
1983 }
1984 }
1985 }
1986 }
1987 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1989 # TODO: Find a way to post the 'create_packages_list_db' event
1990 if(not defined($dont_create_packages_list)) {
1991 &create_packages_list_db(undef, undef, $session_id);
1992 }
1993 }
1995 $ldap_handle->disconnect;
1996 return $result;
1997 }
2000 sub run_create_fai_release_db {
2001 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2002 my $session_id = $session->ID;
2003 my $task = POE::Wheel::Run->new(
2004 Program => sub { &create_fai_release_db($table_name, $session_id) },
2005 StdoutEvent => "session_run_result",
2006 StderrEvent => "session_run_debug",
2007 CloseEvent => "session_run_done",
2008 );
2010 $heap->{task}->{ $task->ID } = $task;
2011 return;
2012 }
2015 sub create_fai_release_db {
2016 my ($table_name, $session_id) = @_;
2017 my $result;
2019 # used for logging
2020 if (not defined $session_id) { $session_id = 0; }
2022 my $ldap_handle = &get_ldap_handle();
2023 if(defined($ldap_handle)) {
2024 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2025 my $mesg= $ldap_handle->search(
2026 base => $ldap_base,
2027 scope => 'sub',
2028 attrs => [],
2029 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2030 );
2031 if($mesg->{'resultCode'} == 0 &&
2032 $mesg->count != 0) {
2033 # Walk through all possible FAI container ou's
2034 my @sql_list;
2035 my $timestamp= &get_time();
2036 foreach my $ou (@{$mesg->{entries}}) {
2037 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2038 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2039 my @tmp_array=get_fai_release_entries($tmp_classes);
2040 if(@tmp_array) {
2041 foreach my $entry (@tmp_array) {
2042 if(defined($entry) && ref($entry) eq 'HASH') {
2043 my $sql=
2044 "INSERT INTO $table_name "
2045 ."(timestamp, release, class, type, state) VALUES ("
2046 .$timestamp.","
2047 ."'".$entry->{'release'}."',"
2048 ."'".$entry->{'class'}."',"
2049 ."'".$entry->{'type'}."',"
2050 ."'".$entry->{'state'}."')";
2051 push @sql_list, $sql;
2052 }
2053 }
2054 }
2055 }
2056 }
2058 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2059 if(@sql_list) {
2060 unshift @sql_list, "VACUUM";
2061 unshift @sql_list, "DELETE FROM $table_name";
2062 $fai_release_db->exec_statementlist(\@sql_list);
2063 }
2064 daemon_log("$session_id DEBUG: Done with inserting",7);
2065 }
2066 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2067 }
2068 $ldap_handle->disconnect;
2069 return $result;
2070 }
2072 sub get_fai_types {
2073 my $tmp_classes = shift || return undef;
2074 my @result;
2076 foreach my $type(keys %{$tmp_classes}) {
2077 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2078 my $entry = {
2079 type => $type,
2080 state => $tmp_classes->{$type}[0],
2081 };
2082 push @result, $entry;
2083 }
2084 }
2086 return @result;
2087 }
2089 sub get_fai_state {
2090 my $result = "";
2091 my $tmp_classes = shift || return $result;
2093 foreach my $type(keys %{$tmp_classes}) {
2094 if(defined($tmp_classes->{$type}[0])) {
2095 $result = $tmp_classes->{$type}[0];
2097 # State is equal for all types in class
2098 last;
2099 }
2100 }
2102 return $result;
2103 }
2105 sub resolve_fai_classes {
2106 my ($fai_base, $ldap_handle, $session_id) = @_;
2107 if (not defined $session_id) { $session_id = 0; }
2108 my $result;
2109 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2110 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2111 my $fai_classes;
2113 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2114 my $mesg= $ldap_handle->search(
2115 base => $fai_base,
2116 scope => 'sub',
2117 attrs => ['cn','objectClass','FAIstate'],
2118 filter => $fai_filter,
2119 );
2120 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2122 if($mesg->{'resultCode'} == 0 &&
2123 $mesg->count != 0) {
2124 foreach my $entry (@{$mesg->{entries}}) {
2125 if($entry->exists('cn')) {
2126 my $tmp_dn= $entry->dn();
2128 # Skip classname and ou dn parts for class
2129 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2131 # Skip classes without releases
2132 if((!defined($tmp_release)) || length($tmp_release)==0) {
2133 next;
2134 }
2136 my $tmp_cn= $entry->get_value('cn');
2137 my $tmp_state= $entry->get_value('FAIstate');
2139 my $tmp_type;
2140 # Get FAI type
2141 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2142 if(grep $_ eq $oclass, @possible_fai_classes) {
2143 $tmp_type= $oclass;
2144 last;
2145 }
2146 }
2148 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2149 # A Subrelease
2150 my @sub_releases = split(/,/, $tmp_release);
2152 # Walk through subreleases and build hash tree
2153 my $hash;
2154 while(my $tmp_sub_release = pop @sub_releases) {
2155 $hash .= "\{'$tmp_sub_release'\}->";
2156 }
2157 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2158 } else {
2159 # A branch, no subrelease
2160 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2161 }
2162 } elsif (!$entry->exists('cn')) {
2163 my $tmp_dn= $entry->dn();
2164 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2166 # Skip classes without releases
2167 if((!defined($tmp_release)) || length($tmp_release)==0) {
2168 next;
2169 }
2171 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2172 # A Subrelease
2173 my @sub_releases= split(/,/, $tmp_release);
2175 # Walk through subreleases and build hash tree
2176 my $hash;
2177 while(my $tmp_sub_release = pop @sub_releases) {
2178 $hash .= "\{'$tmp_sub_release'\}->";
2179 }
2180 # Remove the last two characters
2181 chop($hash);
2182 chop($hash);
2184 eval('$fai_classes->'.$hash.'= {}');
2185 } else {
2186 # A branch, no subrelease
2187 if(!exists($fai_classes->{$tmp_release})) {
2188 $fai_classes->{$tmp_release} = {};
2189 }
2190 }
2191 }
2192 }
2194 # The hash is complete, now we can honor the copy-on-write based missing entries
2195 foreach my $release (keys %$fai_classes) {
2196 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2197 }
2198 }
2199 return $result;
2200 }
2202 sub apply_fai_inheritance {
2203 my $fai_classes = shift || return {};
2204 my $tmp_classes;
2206 # Get the classes from the branch
2207 foreach my $class (keys %{$fai_classes}) {
2208 # Skip subreleases
2209 if($class =~ /^ou=.*$/) {
2210 next;
2211 } else {
2212 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2213 }
2214 }
2216 # Apply to each subrelease
2217 foreach my $subrelease (keys %{$fai_classes}) {
2218 if($subrelease =~ /ou=/) {
2219 foreach my $tmp_class (keys %{$tmp_classes}) {
2220 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2221 $fai_classes->{$subrelease}->{$tmp_class} =
2222 deep_copy($tmp_classes->{$tmp_class});
2223 } else {
2224 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2225 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2226 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2227 deep_copy($tmp_classes->{$tmp_class}->{$type});
2228 }
2229 }
2230 }
2231 }
2232 }
2233 }
2235 # Find subreleases in deeper levels
2236 foreach my $subrelease (keys %{$fai_classes}) {
2237 if($subrelease =~ /ou=/) {
2238 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2239 if($subsubrelease =~ /ou=/) {
2240 apply_fai_inheritance($fai_classes->{$subrelease});
2241 }
2242 }
2243 }
2244 }
2246 return $fai_classes;
2247 }
2249 sub get_fai_release_entries {
2250 my $tmp_classes = shift || return;
2251 my $parent = shift || "";
2252 my @result = shift || ();
2254 foreach my $entry (keys %{$tmp_classes}) {
2255 if(defined($entry)) {
2256 if($entry =~ /^ou=.*$/) {
2257 my $release_name = $entry;
2258 $release_name =~ s/ou=//g;
2259 if(length($parent)>0) {
2260 $release_name = $parent."/".$release_name;
2261 }
2262 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2263 foreach my $bufentry(@bufentries) {
2264 push @result, $bufentry;
2265 }
2266 } else {
2267 my @types = get_fai_types($tmp_classes->{$entry});
2268 foreach my $type (@types) {
2269 push @result,
2270 {
2271 'class' => $entry,
2272 'type' => $type->{'type'},
2273 'release' => $parent,
2274 'state' => $type->{'state'},
2275 };
2276 }
2277 }
2278 }
2279 }
2281 return @result;
2282 }
2284 sub deep_copy {
2285 my $this = shift;
2286 if (not ref $this) {
2287 $this;
2288 } elsif (ref $this eq "ARRAY") {
2289 [map deep_copy($_), @$this];
2290 } elsif (ref $this eq "HASH") {
2291 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2292 } else { die "what type is $_?" }
2293 }
2296 sub session_run_result {
2297 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2298 $kernel->sig(CHLD => "child_reap");
2299 }
2301 sub session_run_debug {
2302 my $result = $_[ARG0];
2303 print STDERR "$result\n";
2304 }
2306 sub session_run_done {
2307 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2308 delete $heap->{task}->{$task_id};
2309 }
2312 sub create_sources_list {
2313 my $session_id = shift;
2314 my $ldap_handle = &main::get_ldap_handle;
2315 my $result="/tmp/gosa_si_tmp_sources_list";
2317 # Remove old file
2318 if(stat($result)) {
2319 unlink($result);
2320 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2321 }
2323 my $fh;
2324 open($fh, ">$result");
2325 if (not defined $fh) {
2326 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2327 return undef;
2328 }
2329 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2330 my $mesg=$ldap_handle->search(
2331 base => $main::ldap_server_dn,
2332 scope => 'base',
2333 attrs => 'FAIrepository',
2334 filter => 'objectClass=FAIrepositoryServer'
2335 );
2336 if($mesg->count) {
2337 foreach my $entry(@{$mesg->{'entries'}}) {
2338 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2339 my ($server, $tag, $release, $sections)= split /\|/, $value;
2340 my $line = "deb $server $release";
2341 $sections =~ s/,/ /g;
2342 $line.= " $sections";
2343 print $fh $line."\n";
2344 }
2345 }
2346 }
2347 } else {
2348 if (defined $main::ldap_server_dn){
2349 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2350 } else {
2351 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2352 }
2353 }
2354 close($fh);
2356 return $result;
2357 }
2360 sub run_create_packages_list_db {
2361 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2362 my $session_id = $session->ID;
2364 my $task = POE::Wheel::Run->new(
2365 Priority => +20,
2366 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2367 StdoutEvent => "session_run_result",
2368 StderrEvent => "session_run_debug",
2369 CloseEvent => "session_run_done",
2370 );
2371 $heap->{task}->{ $task->ID } = $task;
2372 }
2375 sub create_packages_list_db {
2376 my ($ldap_handle, $sources_file, $session_id) = @_;
2378 # it should not be possible to trigger a recreation of packages_list_db
2379 # while packages_list_db is under construction, so set flag packages_list_under_construction
2380 # which is tested befor recreation can be started
2381 if (-r $packages_list_under_construction) {
2382 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2383 return;
2384 } else {
2385 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2386 # set packages_list_under_construction to true
2387 system("touch $packages_list_under_construction");
2388 @packages_list_statements=();
2389 }
2391 if (not defined $session_id) { $session_id = 0; }
2392 if (not defined $ldap_handle) {
2393 $ldap_handle= &get_ldap_handle();
2395 if (not defined $ldap_handle) {
2396 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2397 unlink($packages_list_under_construction);
2398 return;
2399 }
2400 }
2401 if (not defined $sources_file) {
2402 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2403 $sources_file = &create_sources_list($session_id);
2404 }
2406 if (not defined $sources_file) {
2407 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2408 unlink($packages_list_under_construction);
2409 return;
2410 }
2412 my $line;
2414 open(CONFIG, "<$sources_file") or do {
2415 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2416 unlink($packages_list_under_construction);
2417 return;
2418 };
2420 # Read lines
2421 while ($line = <CONFIG>){
2422 # Unify
2423 chop($line);
2424 $line =~ s/^\s+//;
2425 $line =~ s/^\s+/ /;
2427 # Strip comments
2428 $line =~ s/#.*$//g;
2430 # Skip empty lines
2431 if ($line =~ /^\s*$/){
2432 next;
2433 }
2435 # Interpret deb line
2436 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2437 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2438 my $section;
2439 foreach $section (split(' ', $sections)){
2440 &parse_package_info( $baseurl, $dist, $section, $session_id );
2441 }
2442 }
2443 }
2445 close (CONFIG);
2447 find(\&cleanup_and_extract, keys( %repo_dirs ));
2448 &main::strip_packages_list_statements();
2449 unshift @packages_list_statements, "VACUUM";
2450 $packages_list_db->exec_statementlist(\@packages_list_statements);
2451 unlink($packages_list_under_construction);
2452 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2453 return;
2454 }
2456 # This function should do some intensive task to minimize the db-traffic
2457 sub strip_packages_list_statements {
2458 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2459 my @new_statement_list=();
2460 my $hash;
2461 my $insert_hash;
2462 my $update_hash;
2463 my $delete_hash;
2464 my $local_timestamp=get_time();
2466 foreach my $existing_entry (@existing_entries) {
2467 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2468 }
2470 foreach my $statement (@packages_list_statements) {
2471 if($statement =~ /^INSERT/i) {
2472 # Assign the values from the insert statement
2473 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2474 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2475 if(exists($hash->{$distribution}->{$package}->{$version})) {
2476 # If section or description has changed, update the DB
2477 if(
2478 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2479 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2480 ) {
2481 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2482 }
2483 } else {
2484 # Insert a non-existing entry to db
2485 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2486 }
2487 } elsif ($statement =~ /^UPDATE/i) {
2488 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2489 /^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;
2490 foreach my $distribution (keys %{$hash}) {
2491 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2492 # update the insertion hash to execute only one query per package (insert instead insert+update)
2493 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2494 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2495 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2496 my $section;
2497 my $description;
2498 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2499 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2500 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2501 }
2502 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2503 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2504 }
2505 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2506 }
2507 }
2508 }
2509 }
2510 }
2512 # TODO: Check for orphaned entries
2514 # unroll the insert_hash
2515 foreach my $distribution (keys %{$insert_hash}) {
2516 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2517 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2518 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2519 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2520 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2521 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2522 ."'$local_timestamp')";
2523 }
2524 }
2525 }
2527 # unroll the update hash
2528 foreach my $distribution (keys %{$update_hash}) {
2529 foreach my $package (keys %{$update_hash->{$distribution}}) {
2530 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2531 my $set = "";
2532 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2533 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2534 }
2535 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2536 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2537 }
2538 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2539 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2540 }
2541 if(defined($set) and length($set) > 0) {
2542 $set .= "timestamp = '$local_timestamp'";
2543 } else {
2544 next;
2545 }
2546 push @new_statement_list,
2547 "UPDATE $main::packages_list_tn SET $set WHERE"
2548 ." distribution = '$distribution'"
2549 ." AND package = '$package'"
2550 ." AND version = '$version'";
2551 }
2552 }
2553 }
2555 @packages_list_statements = @new_statement_list;
2556 }
2559 sub parse_package_info {
2560 my ($baseurl, $dist, $section, $session_id)= @_;
2561 my ($package);
2562 if (not defined $session_id) { $session_id = 0; }
2563 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2564 $repo_dirs{ "${repo_path}/pool" } = 1;
2566 foreach $package ("Packages.gz"){
2567 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2568 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2569 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2570 }
2572 }
2575 sub get_package {
2576 my ($url, $dest, $session_id)= @_;
2577 if (not defined $session_id) { $session_id = 0; }
2579 my $tpath = dirname($dest);
2580 -d "$tpath" || mkpath "$tpath";
2582 # This is ugly, but I've no time to take a look at "how it works in perl"
2583 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2584 system("gunzip -cd '$dest' > '$dest.in'");
2585 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2586 unlink($dest);
2587 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2588 } else {
2589 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2590 }
2591 return 0;
2592 }
2595 sub parse_package {
2596 my ($path, $dist, $srv_path, $session_id)= @_;
2597 if (not defined $session_id) { $session_id = 0;}
2598 my ($package, $version, $section, $description);
2599 my $PACKAGES;
2600 my $timestamp = &get_time();
2602 if(not stat("$path.in")) {
2603 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2604 return;
2605 }
2607 open($PACKAGES, "<$path.in");
2608 if(not defined($PACKAGES)) {
2609 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2610 return;
2611 }
2613 # Read lines
2614 while (<$PACKAGES>){
2615 my $line = $_;
2616 # Unify
2617 chop($line);
2619 # Use empty lines as a trigger
2620 if ($line =~ /^\s*$/){
2621 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2622 push(@packages_list_statements, $sql);
2623 $package = "none";
2624 $version = "none";
2625 $section = "none";
2626 $description = "none";
2627 next;
2628 }
2630 # Trigger for package name
2631 if ($line =~ /^Package:\s/){
2632 ($package)= ($line =~ /^Package: (.*)$/);
2633 next;
2634 }
2636 # Trigger for version
2637 if ($line =~ /^Version:\s/){
2638 ($version)= ($line =~ /^Version: (.*)$/);
2639 next;
2640 }
2642 # Trigger for description
2643 if ($line =~ /^Description:\s/){
2644 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2645 next;
2646 }
2648 # Trigger for section
2649 if ($line =~ /^Section:\s/){
2650 ($section)= ($line =~ /^Section: (.*)$/);
2651 next;
2652 }
2654 # Trigger for filename
2655 if ($line =~ /^Filename:\s/){
2656 my ($filename) = ($line =~ /^Filename: (.*)$/);
2657 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2658 next;
2659 }
2660 }
2662 close( $PACKAGES );
2663 unlink( "$path.in" );
2664 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2665 }
2668 sub store_fileinfo {
2669 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2671 my %fileinfo = (
2672 'package' => $package,
2673 'dist' => $dist,
2674 'version' => $vers,
2675 );
2677 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2678 }
2681 sub cleanup_and_extract {
2682 my $fileinfo = $repo_files{ $File::Find::name };
2684 if( defined $fileinfo ) {
2686 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2687 my $sql;
2688 my $package = $fileinfo->{ 'package' };
2689 my $newver = $fileinfo->{ 'version' };
2691 mkpath($dir);
2692 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2694 if( -f "$dir/DEBIAN/templates" ) {
2696 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2698 my $tmpl= "";
2699 {
2700 local $/=undef;
2701 open FILE, "$dir/DEBIAN/templates";
2702 $tmpl = &encode_base64(<FILE>);
2703 close FILE;
2704 }
2705 rmtree("$dir/DEBIAN/templates");
2707 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2708 push @packages_list_statements, $sql;
2709 }
2710 }
2712 return;
2713 }
2716 sub register_at_foreign_servers {
2717 my ($kernel) = $_[KERNEL];
2719 # hole alle bekannten server aus known_server_db
2720 my $server_sql = "SELECT * FROM $known_server_tn";
2721 my $server_res = $known_server_db->exec_statement($server_sql);
2723 # no entries in known_server_db
2724 if (not ref(@$server_res[0]) eq "ARRAY") {
2725 # TODO
2726 }
2728 # detect already connected clients
2729 my $client_sql = "SELECT * FROM $known_clients_tn";
2730 my $client_res = $known_clients_db->exec_statement($client_sql);
2732 # send my server details to all other gosa-si-server within the network
2733 foreach my $hit (@$server_res) {
2734 my $hostname = @$hit[0];
2735 my $hostkey = &create_passwd;
2737 # add already connected clients to registration message
2738 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2739 &add_content2xml_hash($myhash, 'key', $hostkey);
2740 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2742 # build registration message and send it
2743 my $foreign_server_msg = &create_xml_string($myhash);
2744 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2745 }
2747 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2748 return;
2749 }
2752 #==== MAIN = main ==============================================================
2753 # parse commandline options
2754 Getopt::Long::Configure( "bundling" );
2755 GetOptions("h|help" => \&usage,
2756 "c|config=s" => \$cfg_file,
2757 "f|foreground" => \$foreground,
2758 "v|verbose+" => \$verbose,
2759 "no-bus+" => \$no_bus,
2760 "no-arp+" => \$no_arp,
2761 );
2763 # read and set config parameters
2764 &check_cmdline_param ;
2765 &read_configfile;
2766 &check_pid;
2768 $SIG{CHLD} = 'IGNORE';
2770 # forward error messages to logfile
2771 if( ! $foreground ) {
2772 open( STDIN, '+>/dev/null' );
2773 open( STDOUT, '+>&STDIN' );
2774 open( STDERR, '+>&STDIN' );
2775 }
2777 # Just fork, if we are not in foreground mode
2778 if( ! $foreground ) {
2779 chdir '/' or die "Can't chdir to /: $!";
2780 $pid = fork;
2781 setsid or die "Can't start a new session: $!";
2782 umask 0;
2783 } else {
2784 $pid = $$;
2785 }
2787 # Do something useful - put our PID into the pid_file
2788 if( 0 != $pid ) {
2789 open( LOCK_FILE, ">$pid_file" );
2790 print LOCK_FILE "$pid\n";
2791 close( LOCK_FILE );
2792 if( !$foreground ) {
2793 exit( 0 )
2794 };
2795 }
2797 # parse head url and revision from svn
2798 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2799 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2800 $server_headURL = defined $1 ? $1 : 'unknown' ;
2801 $server_revision = defined $2 ? $2 : 'unknown' ;
2802 if ($server_headURL =~ /\/tag\// ||
2803 $server_headURL =~ /\/branches\// ) {
2804 $server_status = "stable";
2805 } else {
2806 $server_status = "developmental" ;
2807 }
2810 daemon_log(" ", 1);
2811 daemon_log("$0 started!", 1);
2812 daemon_log("status: $server_status", 1);
2813 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2815 if ($no_bus > 0) {
2816 $bus_activ = "false"
2817 }
2819 # connect to incoming_db
2820 unlink($incoming_file_name);
2821 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2822 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2824 # connect to gosa-si job queue
2825 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2826 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2828 # connect to known_clients_db
2829 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2830 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2832 # connect to foreign_clients_db
2833 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2834 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2836 # connect to known_server_db
2837 unlink($known_server_file_name);
2838 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2839 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2841 # connect to login_usr_db
2842 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2843 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2845 # connect to fai_server_db and fai_release_db
2846 unlink($fai_server_file_name);
2847 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2848 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2850 unlink($fai_release_file_name);
2851 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2852 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2854 # connect to packages_list_db
2855 #unlink($packages_list_file_name);
2856 unlink($packages_list_under_construction);
2857 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2858 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2860 # connect to messaging_db
2861 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2862 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2865 # create xml object used for en/decrypting
2866 $xml = new XML::Simple();
2869 # foreign servers
2870 my @foreign_server_list;
2872 # add foreign server from cfg file
2873 if ($foreign_server_string ne "") {
2874 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2875 foreach my $foreign_server (@cfg_foreign_server_list) {
2876 push(@foreign_server_list, $foreign_server);
2877 }
2878 }
2880 # add foreign server from dns
2881 my @tmp_servers;
2882 if ( !$server_domain) {
2883 # Try our DNS Searchlist
2884 for my $domain(get_dns_domains()) {
2885 chomp($domain);
2886 my @tmp_domains= &get_server_addresses($domain);
2887 if(@tmp_domains) {
2888 for my $tmp_server(@tmp_domains) {
2889 push @tmp_servers, $tmp_server;
2890 }
2891 }
2892 }
2893 if(@tmp_servers && length(@tmp_servers)==0) {
2894 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2895 }
2896 } else {
2897 @tmp_servers = &get_server_addresses($server_domain);
2898 if( 0 == @tmp_servers ) {
2899 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2900 }
2901 }
2902 foreach my $server (@tmp_servers) {
2903 unshift(@foreign_server_list, $server);
2904 }
2905 # eliminate duplicate entries
2906 @foreign_server_list = &del_doubles(@foreign_server_list);
2907 my $all_foreign_server = join(", ", @foreign_server_list);
2908 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2910 # add all found foreign servers to known_server
2911 my $act_timestamp = &get_time();
2912 foreach my $foreign_server (@foreign_server_list) {
2913 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2914 primkey=>['hostname'],
2915 hostname=>$foreign_server,
2916 status=>'not_jet_registered',
2917 hostkey=>"none",
2918 timestamp=>$act_timestamp,
2919 } );
2920 }
2923 POE::Component::Server::TCP->new(
2924 Alias => "TCP_SERVER",
2925 Port => $server_port,
2926 ClientInput => sub {
2927 my ($kernel, $input) = @_[KERNEL, ARG0];
2928 push(@tasks, $input);
2929 push(@msgs_to_decrypt, $input);
2930 $kernel->yield("msg_to_decrypt");
2931 },
2932 InlineStates => {
2933 msg_to_decrypt => \&msg_to_decrypt,
2934 next_task => \&next_task,
2935 task_result => \&handle_task_result,
2936 task_done => \&handle_task_done,
2937 task_debug => \&handle_task_debug,
2938 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2939 }
2940 );
2942 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2944 # create session for repeatedly checking the job queue for jobs
2945 POE::Session->create(
2946 inline_states => {
2947 _start => \&session_start,
2948 register_at_foreign_servers => \®ister_at_foreign_servers,
2949 sig_handler => \&sig_handler,
2950 next_task => \&next_task,
2951 task_result => \&handle_task_result,
2952 task_done => \&handle_task_done,
2953 task_debug => \&handle_task_debug,
2954 watch_for_next_tasks => \&watch_for_next_tasks,
2955 watch_for_new_messages => \&watch_for_new_messages,
2956 watch_for_delivery_messages => \&watch_for_delivery_messages,
2957 watch_for_done_messages => \&watch_for_done_messages,
2958 watch_for_new_jobs => \&watch_for_new_jobs,
2959 watch_for_done_jobs => \&watch_for_done_jobs,
2960 watch_for_old_known_clients => \&watch_for_old_known_clients,
2961 create_packages_list_db => \&run_create_packages_list_db,
2962 create_fai_server_db => \&run_create_fai_server_db,
2963 create_fai_release_db => \&run_create_fai_release_db,
2964 session_run_result => \&session_run_result,
2965 session_run_debug => \&session_run_debug,
2966 session_run_done => \&session_run_done,
2967 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2968 }
2969 );
2972 # import all modules
2973 &import_modules;
2975 # TODO
2976 # check wether all modules are gosa-si valid passwd check
2980 POE::Kernel->run();
2981 exit;