f9d18b5b160b1faa70f5d3167668e362c2a33310
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 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5 qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
56 # TODO es gibt eine globale funktion get_ldap_handle
57 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
58 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
59 # - sessions die kein ldap handle brauchen, sollen auch keins haben
60 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
61 our $global_kernel;
63 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
64 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
65 my ($server);
66 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
67 my ($messaging_db_loop_delay);
68 my ($known_modules);
69 my ($pid_file, $procid, $pid, $log_file);
70 my ($arp_activ, $arp_fifo);
71 my ($xml);
72 my $sources_list;
73 my $max_clients;
74 my %repo_files=();
75 my $repo_path;
76 my %repo_dirs=();
77 # variables declared in config file are always set to 'our'
78 our (%cfg_defaults, $log_file, $pid_file,
79 $server_ip, $server_port, $SIPackages_key,
80 $arp_activ, $gosa_unit_tag,
81 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
82 );
84 # additional variable which should be globaly accessable
85 our $server_address;
86 our $server_mac_address;
87 our $bus_address;
88 our $gosa_address;
89 our $no_bus;
90 our $no_arp;
91 our $verbose;
92 our $forground;
93 our $cfg_file;
94 #our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
95 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
98 # specifies the verbosity of the daemon_log
99 $verbose = 0 ;
101 # if foreground is not null, script will be not forked to background
102 $foreground = 0 ;
104 # specifies the timeout seconds while checking the online status of a registrating client
105 $ping_timeout = 5;
107 $no_bus = 0;
108 $bus_activ = "true";
109 $no_arp = 0;
110 my $packages_list_under_construction = 0;
112 our $prg= basename($0);
114 # holds all gosa jobs
115 our $job_db;
116 our $job_queue_tn = 'jobs';
117 my $job_queue_file_name;
118 my @job_queue_col_names = ("id INTEGER",
119 "timestamp",
120 "status DEFAULT 'none'",
121 "result DEFAULT 'none'",
122 "progress DEFAULT 'none'",
123 "headertag DEFAULT 'none'",
124 "targettag DEFAULT 'none'",
125 "xmlmessage DEFAULT 'none'",
126 "macaddress DEFAULT 'none'",
127 "plainname DEFAULT 'none'",
128 );
130 # holds all other gosa-sd as well as the gosa-sd-bus
131 our $known_server_db;
132 our $known_server_tn = "known_server";
133 my $known_server_file_name;
134 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
136 # holds all registrated clients
137 our $known_clients_db;
138 our $known_clients_tn = "known_clients";
139 my $known_clients_file_name;
140 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
142 # holds all logged in user at each client
143 our $login_users_db;
144 our $login_users_tn = "login_users";
145 my $login_users_file_name;
146 my @login_users_col_names = ('client', 'user', 'timestamp');
148 # holds all fai server, the debian release and tag
149 our $fai_server_db;
150 our $fai_server_tn = "fai_server";
151 my $fai_server_file_name;
152 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag');
154 our $fai_release_db;
155 our $fai_release_tn = "fai_release";
156 my $fai_release_file_name;
157 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state');
159 # holds all packages available from different repositories
160 our $packages_list_db;
161 our $packages_list_tn = "packages_list";
162 my $packages_list_file_name;
163 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
164 my $outdir = "/tmp/packages_list_db";
165 my $arch = "i386";
167 # holds all messages which should be delivered to a user
168 our $messaging_db;
169 our $messaging_tn = "messaging";
170 our @messaging_col_names = ('subject', 'message_from', 'message_to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER' );
171 my $messaging_file_name;
173 # path to directory to store client install log files
174 our $client_fai_log_dir = "/var/log/fai";
176 # queue which stores taskes until one of the $max_children children are ready to process the task
177 my @tasks = qw();
178 my $max_children = 2;
181 %cfg_defaults = (
182 "general" => {
183 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
184 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
185 },
186 "bus" => {
187 "activ" => [\$bus_activ, "true"],
188 },
189 "server" => {
190 "port" => [\$server_port, "20081"],
191 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
192 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
193 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
194 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
195 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
196 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
197 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
198 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
199 "repo-path" => [\$repo_path, '/srv/www/repository'],
200 "ldap-uri" => [\$ldap_uri, ""],
201 "ldap-base" => [\$ldap_base, ""],
202 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
203 "ldap-admin-password" => [\$ldap_admin_password, ""],
204 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
205 "max-clients" => [\$max_clients, 10],
206 },
207 "GOsaPackages" => {
208 "ip" => [\$gosa_ip, "0.0.0.0"],
209 "port" => [\$gosa_port, "20082"],
210 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
211 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
212 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
213 "key" => [\$GosaPackages_key, "none"],
214 },
215 "SIPackages" => {
216 "key" => [\$SIPackages_key, "none"],
217 },
218 );
221 #=== FUNCTION ================================================================
222 # NAME: usage
223 # PARAMETERS: nothing
224 # RETURNS: nothing
225 # DESCRIPTION: print out usage text to STDERR
226 #===============================================================================
227 sub usage {
228 print STDERR << "EOF" ;
229 usage: $prg [-hvf] [-c config]
231 -h : this (help) message
232 -c <file> : config file
233 -f : foreground, process will not be forked to background
234 -v : be verbose (multiple to increase verbosity)
235 -no-bus : starts $prg without connection to bus
236 -no-arp : starts $prg without connection to arp module
238 EOF
239 print "\n" ;
240 }
243 #=== FUNCTION ================================================================
244 # NAME: read_configfile
245 # PARAMETERS: cfg_file - string -
246 # RETURNS: nothing
247 # DESCRIPTION: read cfg_file and set variables
248 #===============================================================================
249 sub read_configfile {
250 my $cfg;
251 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
252 if( -r $cfg_file ) {
253 $cfg = Config::IniFiles->new( -file => $cfg_file );
254 } else {
255 print STDERR "Couldn't read config file!\n";
256 }
257 } else {
258 $cfg = Config::IniFiles->new() ;
259 }
260 foreach my $section (keys %cfg_defaults) {
261 foreach my $param (keys %{$cfg_defaults{ $section }}) {
262 my $pinfo = $cfg_defaults{ $section }{ $param };
263 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
264 }
265 }
266 }
269 #=== FUNCTION ================================================================
270 # NAME: logging
271 # PARAMETERS: level - string - default 'info'
272 # msg - string -
273 # facility - string - default 'LOG_DAEMON'
274 # RETURNS: nothing
275 # DESCRIPTION: function for logging
276 #===============================================================================
277 sub daemon_log {
278 # log into log_file
279 my( $msg, $level ) = @_;
280 if(not defined $msg) { return }
281 if(not defined $level) { $level = 1 }
282 if(defined $log_file){
283 open(LOG_HANDLE, ">>$log_file");
284 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
285 print STDERR "cannot open $log_file: $!";
286 return }
287 chomp($msg);
288 if($level <= $verbose){
289 my ($seconds, $minutes, $hours, $monthday, $month,
290 $year, $weekday, $yearday, $sommertime) = localtime(time);
291 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
292 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
293 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
294 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
295 $month = $monthnames[$month];
296 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
297 $year+=1900;
298 my $name = $prg;
300 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
301 print LOG_HANDLE $log_msg;
302 if( $foreground ) {
303 print STDERR $log_msg;
304 }
305 }
306 close( LOG_HANDLE );
307 }
308 }
311 #=== FUNCTION ================================================================
312 # NAME: check_cmdline_param
313 # PARAMETERS: nothing
314 # RETURNS: nothing
315 # DESCRIPTION: validates commandline parameter
316 #===============================================================================
317 sub check_cmdline_param () {
318 my $err_config;
319 my $err_counter = 0;
320 if(not defined($cfg_file)) {
321 $cfg_file = "/etc/gosa-si/server.conf";
322 if(! -r $cfg_file) {
323 $err_config = "please specify a config file";
324 $err_counter += 1;
325 }
326 }
327 if( $err_counter > 0 ) {
328 &usage( "", 1 );
329 if( defined( $err_config)) { print STDERR "$err_config\n"}
330 print STDERR "\n";
331 exit( -1 );
332 }
333 }
336 #=== FUNCTION ================================================================
337 # NAME: check_pid
338 # PARAMETERS: nothing
339 # RETURNS: nothing
340 # DESCRIPTION: handels pid processing
341 #===============================================================================
342 sub check_pid {
343 $pid = -1;
344 # Check, if we are already running
345 if( open(LOCK_FILE, "<$pid_file") ) {
346 $pid = <LOCK_FILE>;
347 if( defined $pid ) {
348 chomp( $pid );
349 if( -f "/proc/$pid/stat" ) {
350 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
351 if( $stat ) {
352 daemon_log("ERROR: Already running",1);
353 close( LOCK_FILE );
354 exit -1;
355 }
356 }
357 }
358 close( LOCK_FILE );
359 unlink( $pid_file );
360 }
362 # create a syslog msg if it is not to possible to open PID file
363 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
364 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
365 if (open(LOCK_FILE, '<', $pid_file)
366 && ($pid = <LOCK_FILE>))
367 {
368 chomp($pid);
369 $msg .= "(PID $pid)\n";
370 } else {
371 $msg .= "(unable to read PID)\n";
372 }
373 if( ! ($foreground) ) {
374 openlog( $0, "cons,pid", "daemon" );
375 syslog( "warning", $msg );
376 closelog();
377 }
378 else {
379 print( STDERR " $msg " );
380 }
381 exit( -1 );
382 }
383 }
385 #=== FUNCTION ================================================================
386 # NAME: import_modules
387 # PARAMETERS: module_path - string - abs. path to the directory the modules
388 # are stored
389 # RETURNS: nothing
390 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
391 # state is on is imported by "require 'file';"
392 #===============================================================================
393 sub import_modules {
394 daemon_log(" ", 1);
396 if (not -e $modules_path) {
397 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
398 }
400 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
401 while (defined (my $file = readdir (DIR))) {
402 if (not $file =~ /(\S*?).pm$/) {
403 next;
404 }
405 my $mod_name = $1;
407 if( $file =~ /ArpHandler.pm/ ) {
408 if( $no_arp > 0 ) {
409 next;
410 }
411 }
413 eval { require $file; };
414 if ($@) {
415 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
416 daemon_log("$@", 5);
417 } else {
418 my $info = eval($mod_name.'::get_module_info()');
419 # Only load module if get_module_info() returns a non-null object
420 if( $info ) {
421 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
422 $known_modules->{$mod_name} = $info;
423 daemon_log("INFO: module $mod_name loaded", 5);
424 }
425 }
426 }
427 close (DIR);
428 }
431 #=== FUNCTION ================================================================
432 # NAME: sig_int_handler
433 # PARAMETERS: signal - string - signal arose from system
434 # RETURNS: noting
435 # DESCRIPTION: handels tasks to be done befor signal becomes active
436 #===============================================================================
437 sub sig_int_handler {
438 my ($signal) = @_;
440 # if (defined($ldap_handle)) {
441 # $ldap_handle->disconnect;
442 # }
443 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
446 daemon_log("shutting down gosa-si-server", 1);
447 system("kill `ps -C gosa-si-server -o pid=`");
448 }
449 $SIG{INT} = \&sig_int_handler;
452 sub check_key_and_xml_validity {
453 my ($crypted_msg, $module_key, $session_id) = @_;
454 my $msg;
455 my $msg_hash;
456 my $error_string;
457 eval{
458 $msg = &decrypt_msg($crypted_msg, $module_key);
460 if ($msg =~ /<xml>/i){
461 $msg =~ s/\s+/ /g; # just for better daemon_log
462 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
463 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
465 ##############
466 # check header
467 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
468 my $header_l = $msg_hash->{'header'};
469 if( 1 > @{$header_l} ) { die 'empty header tag'; }
470 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
471 my $header = @{$header_l}[0];
472 if( 0 == length $header) { die 'empty string in header tag'; }
474 ##############
475 # check source
476 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
477 my $source_l = $msg_hash->{'source'};
478 if( 1 > @{$source_l} ) { die 'empty source tag'; }
479 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
480 my $source = @{$source_l}[0];
481 if( 0 == length $source) { die 'source error'; }
483 ##############
484 # check target
485 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
486 my $target_l = $msg_hash->{'target'};
487 if( 1 > @{$target_l} ) { die 'empty target tag'; }
488 }
489 };
490 if($@) {
491 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
492 $msg = undef;
493 $msg_hash = undef;
494 }
496 return ($msg, $msg_hash);
497 }
500 sub check_outgoing_xml_validity {
501 my ($msg) = @_;
503 my $msg_hash;
504 eval{
505 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
507 ##############
508 # check header
509 my $header_l = $msg_hash->{'header'};
510 if( 1 != @{$header_l} ) {
511 die 'no or more than one headers specified';
512 }
513 my $header = @{$header_l}[0];
514 if( 0 == length $header) {
515 die 'header has length 0';
516 }
518 ##############
519 # check source
520 my $source_l = $msg_hash->{'source'};
521 if( 1 != @{$source_l} ) {
522 die 'no or more than 1 sources specified';
523 }
524 my $source = @{$source_l}[0];
525 if( 0 == length $source) {
526 die 'source has length 0';
527 }
528 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
529 $source =~ /^GOSA$/i ) {
530 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
531 }
533 ##############
534 # check target
535 my $target_l = $msg_hash->{'target'};
536 if( 0 == @{$target_l} ) {
537 die "no targets specified";
538 }
539 foreach my $target (@$target_l) {
540 if( 0 == length $target) {
541 die "target has length 0";
542 }
543 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
544 $target =~ /^GOSA$/i ||
545 $target =~ /^\*$/ ||
546 $target =~ /KNOWN_SERVER/i ||
547 $target =~ /JOBDB/i ||
548 $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 ){
549 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
550 }
551 }
552 };
553 if($@) {
554 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
555 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
556 $msg_hash = undef;
557 }
559 return ($msg_hash);
560 }
563 sub input_from_known_server {
564 my ($input, $remote_ip, $session_id) = @_ ;
565 my ($msg, $msg_hash, $module);
567 my $sql_statement= "SELECT * FROM known_server";
568 my $query_res = $known_server_db->select_dbentry( $sql_statement );
570 while( my ($hit_num, $hit) = each %{ $query_res } ) {
571 my $host_name = $hit->{hostname};
572 if( not $host_name =~ "^$remote_ip") {
573 next;
574 }
575 my $host_key = $hit->{hostkey};
576 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
577 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
579 # check if module can open msg envelope with module key
580 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
581 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
582 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
583 daemon_log("$@", 8);
584 next;
585 }
586 else {
587 $msg = $tmp_msg;
588 $msg_hash = $tmp_msg_hash;
589 $module = "SIPackages";
590 last;
591 }
592 }
594 if( (!$msg) || (!$msg_hash) || (!$module) ) {
595 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
596 }
598 return ($msg, $msg_hash, $module);
599 }
602 sub input_from_known_client {
603 my ($input, $remote_ip, $session_id) = @_ ;
604 my ($msg, $msg_hash, $module);
606 my $sql_statement= "SELECT * FROM known_clients";
607 my $query_res = $known_clients_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:\d*$/) {
611 next;
612 }
613 my $host_key = $hit->{hostkey};
614 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
615 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
617 # check if module can open msg envelope with module key
618 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
620 if( (!$msg) || (!$msg_hash) ) {
621 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
622 &daemon_log("$@", 8);
623 next;
624 }
625 else {
626 $module = "SIPackages";
627 last;
628 }
629 }
631 if( (!$msg) || (!$msg_hash) || (!$module) ) {
632 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
633 }
635 return ($msg, $msg_hash, $module);
636 }
639 sub input_from_unknown_host {
640 no strict "refs";
641 my ($input, $session_id) = @_ ;
642 my ($msg, $msg_hash, $module);
643 my $error_string;
645 my %act_modules = %$known_modules;
647 while( my ($mod, $info) = each(%act_modules)) {
649 # check a key exists for this module
650 my $module_key = ${$mod."_key"};
651 if( not defined $module_key ) {
652 if( $mod eq 'ArpHandler' ) {
653 next;
654 }
655 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
656 next;
657 }
658 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
660 # check if module can open msg envelope with module key
661 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
662 if( (not defined $msg) || (not defined $msg_hash) ) {
663 next;
664 }
665 else {
666 $module = $mod;
667 last;
668 }
669 }
671 if( (!$msg) || (!$msg_hash) || (!$module)) {
672 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
673 }
675 return ($msg, $msg_hash, $module);
676 }
679 sub create_ciphering {
680 my ($passwd) = @_;
681 if((!defined($passwd)) || length($passwd)==0) {
682 $passwd = "";
683 }
684 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
685 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
686 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
687 $my_cipher->set_iv($iv);
688 return $my_cipher;
689 }
692 sub encrypt_msg {
693 my ($msg, $key) = @_;
694 my $my_cipher = &create_ciphering($key);
695 my $len;
696 {
697 use bytes;
698 $len= 16-length($msg)%16;
699 }
700 $msg = "\0"x($len).$msg;
701 $msg = $my_cipher->encrypt($msg);
702 chomp($msg = &encode_base64($msg));
703 # there are no newlines allowed inside msg
704 $msg=~ s/\n//g;
705 return $msg;
706 }
709 sub decrypt_msg {
711 my ($msg, $key) = @_ ;
712 $msg = &decode_base64($msg);
713 my $my_cipher = &create_ciphering($key);
714 $msg = $my_cipher->decrypt($msg);
715 $msg =~ s/\0*//g;
716 return $msg;
717 }
720 sub get_encrypt_key {
721 my ($target) = @_ ;
722 my $encrypt_key;
723 my $error = 0;
725 # target can be in known_server
726 if( not defined $encrypt_key ) {
727 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
728 my $query_res = $known_server_db->select_dbentry( $sql_statement );
729 while( my ($hit_num, $hit) = each %{ $query_res } ) {
730 my $host_name = $hit->{hostname};
731 if( $host_name ne $target ) {
732 next;
733 }
734 $encrypt_key = $hit->{hostkey};
735 last;
736 }
737 }
739 # target can be in known_client
740 if( not defined $encrypt_key ) {
741 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
742 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
743 while( my ($hit_num, $hit) = each %{ $query_res } ) {
744 my $host_name = $hit->{hostname};
745 if( $host_name ne $target ) {
746 next;
747 }
748 $encrypt_key = $hit->{hostkey};
749 last;
750 }
751 }
753 return $encrypt_key;
754 }
757 #=== FUNCTION ================================================================
758 # NAME: open_socket
759 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
760 # [PeerPort] string necessary if port not appended by PeerAddr
761 # RETURNS: socket IO::Socket::INET
762 # DESCRIPTION: open a socket to PeerAddr
763 #===============================================================================
764 sub open_socket {
765 my ($PeerAddr, $PeerPort) = @_ ;
766 if(defined($PeerPort)){
767 $PeerAddr = $PeerAddr.":".$PeerPort;
768 }
769 my $socket;
770 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
771 Porto => "tcp",
772 Type => SOCK_STREAM,
773 Timeout => 5,
774 );
775 if(not defined $socket) {
776 return;
777 }
778 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
779 return $socket;
780 }
783 #=== FUNCTION ================================================================
784 # NAME: get_ip
785 # PARAMETERS: interface name (i.e. eth0)
786 # RETURNS: (ip address)
787 # DESCRIPTION: Uses ioctl to get ip address directly from system.
788 #===============================================================================
789 sub get_ip {
790 my $ifreq= shift;
791 my $result= "";
792 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
793 my $proto= getprotobyname('ip');
795 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
796 or die "socket: $!";
798 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
799 my ($if, $sin) = unpack 'a16 a16', $ifreq;
800 my ($port, $addr) = sockaddr_in $sin;
801 my $ip = inet_ntoa $addr;
803 if ($ip && length($ip) > 0) {
804 $result = $ip;
805 }
806 }
808 return $result;
809 }
812 sub get_local_ip_for_remote_ip {
813 my $remote_ip= shift;
814 my $result="0.0.0.0";
816 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
817 if($remote_ip eq "127.0.0.1") {
818 $result = "127.0.0.1";
819 } else {
820 my $PROC_NET_ROUTE= ('/proc/net/route');
822 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
823 or die "Could not open $PROC_NET_ROUTE";
825 my @ifs = <PROC_NET_ROUTE>;
827 close(PROC_NET_ROUTE);
829 # Eat header line
830 shift @ifs;
831 chomp @ifs;
832 foreach my $line(@ifs) {
833 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
834 my $destination;
835 my $mask;
836 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
837 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
838 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
839 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
840 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
841 # destination matches route, save mac and exit
842 $result= &get_ip($Iface);
843 last;
844 }
845 }
846 }
847 } else {
848 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
849 }
850 return $result;
851 }
854 sub send_msg_to_target {
855 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
856 my $error = 0;
857 my $header;
858 my $new_status;
859 my $act_status;
860 my ($sql_statement, $res);
862 if( $msg_header ) {
863 $header = "'$msg_header'-";
864 } else {
865 $header = "";
866 }
868 # Patch the source ip
869 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
870 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
871 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
872 }
874 # encrypt xml msg
875 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
877 # opensocket
878 my $socket = &open_socket($address);
879 if( !$socket ) {
880 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
881 $error++;
882 }
884 if( $error == 0 ) {
885 # send xml msg
886 print $socket $crypted_msg."\n";
888 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
889 #daemon_log("DEBUG: message:\n$msg", 9);
891 }
893 # close socket in any case
894 if( $socket ) {
895 close $socket;
896 }
898 if( $error > 0 ) { $new_status = "down"; }
899 else { $new_status = $msg_header; }
902 # known_clients
903 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
904 $res = $known_clients_db->select_dbentry($sql_statement);
905 if( keys(%$res) > 0) {
906 $act_status = $res->{1}->{'status'};
907 if( $act_status eq "down" ) {
908 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
909 $res = $known_clients_db->del_dbentry($sql_statement);
910 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
911 } else {
912 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
913 $res = $known_clients_db->update_dbentry($sql_statement);
914 if($new_status eq "down"){
915 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
916 } else {
917 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
918 }
919 }
920 }
922 # known_server
923 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
924 $res = $known_server_db->select_dbentry($sql_statement);
925 if( keys(%$res) > 0 ) {
926 $act_status = $res->{1}->{'status'};
927 if( $act_status eq "down" ) {
928 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
929 $res = $known_server_db->del_dbentry($sql_statement);
930 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
931 }
932 else {
933 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
934 $res = $known_server_db->update_dbentry($sql_statement);
935 if($new_status eq "down"){
936 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
937 }
938 else {
939 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
940 }
941 }
942 }
943 return $error;
944 }
947 sub update_jobdb_status_for_send_msgs {
948 my ($answer, $error) = @_;
949 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
950 my $jobdb_id = $1;
952 # sending msg faild
953 if( $error ) {
954 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
955 my $sql_statement = "UPDATE $job_queue_tn ".
956 "SET status='error', result='can not deliver msg, please consult log file' ".
957 "WHERE id='$jobdb_id'";
958 my $res = $job_db->update_dbentry($sql_statement);
959 }
961 # sending msg was successful
962 } else {
963 my $sql_statement = "UPDATE $job_queue_tn ".
964 "SET status='done' ".
965 "WHERE id='$jobdb_id' AND status='processed'";
966 my $res = $job_db->update_dbentry($sql_statement);
967 }
968 }
969 }
971 sub _start {
972 my ($kernel) = $_[KERNEL];
973 &trigger_db_loop($kernel);
974 $global_kernel = $kernel;
975 $kernel->yield('create_fai_server_db', $fai_server_tn );
976 $kernel->yield('create_fai_release_db', $fai_release_tn );
977 $kernel->sig(USR1 => "sig_handler");
978 $kernel->sig(USR2 => "create_packages_list_db");
979 }
981 sub sig_handler {
982 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
983 daemon_log("0 INFO got signal '$signal'", 1);
984 $kernel->sig_handled();
985 return;
986 }
988 sub next_task {
989 my ($session, $heap) = @_[SESSION, HEAP];
991 while ( keys( %{ $heap->{task} } ) < $max_children ) {
992 my $next_task = shift @tasks;
993 last unless defined $next_task;
995 my $task = POE::Wheel::Run->new(
996 Program => sub { process_task($session, $heap, $next_task) },
997 StdioFilter => POE::Filter::Reference->new(),
998 StdoutEvent => "task_result",
999 StderrEvent => "task_debug",
1000 CloseEvent => "task_done",
1001 );
1003 $heap->{task}->{ $task->ID } = $task;
1004 }
1005 }
1007 sub handle_task_result {
1008 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1009 my $client_answer = $result->{'answer'};
1010 if( $client_answer =~ s/session_id=(\d+)$// ) {
1011 my $session_id = $1;
1012 if( defined $session_id ) {
1013 my $session_reference = $kernel->ID_id_to_session($session_id);
1014 if( defined $session_reference ) {
1015 $heap = $session_reference->get_heap();
1016 }
1017 }
1019 if(exists $heap->{'client'}) {
1020 $heap->{'client'}->put($client_answer);
1021 }
1022 }
1023 $kernel->sig(CHLD => "child_reap");
1024 }
1026 sub handle_task_debug {
1027 my $result = $_[ARG0];
1028 print STDERR "$result\n";
1029 }
1031 sub handle_task_done {
1032 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1033 delete $heap->{task}->{$task_id};
1034 $kernel->yield("next_task");
1035 }
1037 sub process_task {
1038 no strict "refs";
1039 my ($session, $heap, $input) = @_;
1040 my $session_id = $session->ID;
1041 my ($msg, $msg_hash, $module);
1042 my $error = 0;
1043 my $answer_l;
1044 my ($answer_header, @answer_target_l, $answer_source);
1045 my $client_answer = "";
1047 daemon_log("", 5);
1048 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1049 daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1051 ####################
1052 # check incoming msg
1053 # msg is from a new client or gosa
1054 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1055 # msg is from a gosa-si-server or gosa-si-bus
1056 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1058 }
1059 # msg is from a gosa-si-client
1060 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1061 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1062 }
1063 # an error occurred
1064 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1065 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1066 # could not understand a msg from its server the client cause a re-registering process
1067 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1068 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1069 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1070 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1071 my $host_name = $hit->{'hostname'};
1072 my $host_key = $hit->{'hostkey'};
1073 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1074 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1075 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1076 }
1077 $error++;
1078 }
1080 ######################
1081 # process incoming msg
1082 if( $error == 0) {
1083 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1084 "' from '".$heap->{'remote_ip'}."'", 5);
1085 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1086 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1088 if ( 0 < @{$answer_l} ) {
1089 my $answer_str = join("\n", @{$answer_l});
1090 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1091 }
1092 }
1093 if( !$answer_l ) { $error++ };
1095 ########
1096 # answer
1097 if( $error == 0 ) {
1099 foreach my $answer ( @{$answer_l} ) {
1100 # for each answer in answer list
1102 # check outgoing msg to xml validity
1103 my $answer_hash = &check_outgoing_xml_validity($answer);
1104 if( not defined $answer_hash ) {
1105 next;
1106 }
1108 $answer_header = @{$answer_hash->{'header'}}[0];
1109 @answer_target_l = @{$answer_hash->{'target'}};
1110 $answer_source = @{$answer_hash->{'source'}}[0];
1112 # deliver msg to all targets
1113 foreach my $answer_target ( @answer_target_l ) {
1115 # targets of msg are all gosa-si-clients in known_clients_db
1116 if( $answer_target eq "*" ) {
1117 # answer is for all clients
1118 my $sql_statement= "SELECT * FROM known_clients";
1119 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1120 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1121 my $host_name = $hit->{hostname};
1122 my $host_key = $hit->{hostkey};
1123 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1124 &update_jobdb_status_for_send_msgs($answer, $error);
1125 }
1126 }
1128 # targets of msg are all gosa-si-server in known_server_db
1129 elsif( $answer_target eq "KNOWN_SERVER" ) {
1130 # answer is for all server in known_server
1131 my $sql_statement= "SELECT * FROM known_server";
1132 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1133 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1134 my $host_name = $hit->{hostname};
1135 my $host_key = $hit->{hostkey};
1136 $answer =~ s/KNOWN_SERVER/$host_name/g;
1137 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1138 &update_jobdb_status_for_send_msgs($answer, $error);
1139 }
1140 }
1142 # target of msg is GOsa
1143 elsif( $answer_target eq "GOSA" ) {
1144 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1145 my $add_on = "";
1146 if( defined $session_id ) {
1147 $add_on = ".session_id=$session_id";
1148 }
1149 # answer is for GOSA and has to returned to connected client
1150 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1151 $client_answer = $gosa_answer.$add_on;
1152 }
1154 # target of msg is job queue at this host
1155 elsif( $answer_target eq "JOBDB") {
1156 $answer =~ /<header>(\S+)<\/header>/;
1157 my $header;
1158 if( defined $1 ) { $header = $1; }
1159 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1160 &update_jobdb_status_for_send_msgs($answer, $error);
1161 }
1163 # target of msg is a mac address
1164 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 ) {
1165 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1166 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1167 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1168 my $found_ip_flag = 0;
1169 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1170 my $host_name = $hit->{hostname};
1171 my $host_key = $hit->{hostkey};
1172 $answer =~ s/$answer_target/$host_name/g;
1173 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1174 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1175 &update_jobdb_status_for_send_msgs($answer, $error);
1176 $found_ip_flag++ ;
1177 }
1178 if( $found_ip_flag == 0) {
1179 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1180 if( $bus_activ eq "true" ) {
1181 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1182 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1183 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1184 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1185 my $bus_address = $hit->{hostname};
1186 my $bus_key = $hit->{hostkey};
1187 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1188 &update_jobdb_status_for_send_msgs($answer, $error);
1189 last;
1190 }
1191 }
1193 }
1195 # answer is for one specific host
1196 } else {
1197 # get encrypt_key
1198 my $encrypt_key = &get_encrypt_key($answer_target);
1199 if( not defined $encrypt_key ) {
1200 # unknown target, forward msg to bus
1201 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1202 if( $bus_activ eq "true" ) {
1203 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1204 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1205 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1206 my $res_length = keys( %{$query_res} );
1207 if( $res_length == 0 ){
1208 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1209 "no bus found in known_server", 3);
1210 }
1211 else {
1212 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1213 my $bus_key = $hit->{hostkey};
1214 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1215 &update_jobdb_status_for_send_msgs($answer, $error);
1216 }
1217 }
1218 }
1219 next;
1220 }
1221 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1222 &update_jobdb_status_for_send_msgs($answer, $error);
1223 }
1224 }
1225 }
1226 }
1228 my $filter = POE::Filter::Reference->new();
1229 my %result = (
1230 status => "seems ok to me",
1231 answer => $client_answer,
1232 );
1234 my $output = $filter->put( [ \%result ] );
1235 print @$output;
1238 }
1241 sub trigger_db_loop {
1242 my ($kernel) = @_ ;
1243 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1244 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1245 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1246 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1247 }
1249 sub watch_for_done_jobs {
1250 my ($kernel,$heap) = @_[KERNEL, HEAP];
1252 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1253 " WHERE status='done'";
1254 my $res = $job_db->select_dbentry( $sql_statement );
1256 while( my ($id, $hit) = each %{$res} ) {
1257 my $jobdb_id = $hit->{id};
1258 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1259 my $res = $job_db->del_dbentry($sql_statement);
1260 }
1262 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1263 }
1265 sub watch_for_new_jobs {
1266 my ($kernel,$heap) = @_[KERNEL, HEAP];
1268 # check gosa job queue for jobs with executable timestamp
1269 my $timestamp = &get_time();
1270 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1271 my $res = $job_db->exec_statement( $sql_statement );
1273 # Merge all new jobs that would do the same actions
1274 my @drops;
1275 my $hits;
1276 foreach my $hit (reverse @{$res} ) {
1277 my $macaddress= lc @{$hit}[8];
1278 my $headertag= @{$hit}[5];
1279 if(defined($hits->{$macaddress}->{$headertag})) {
1280 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1281 }
1282 $hits->{$macaddress}->{$headertag}= $hit;
1283 }
1285 # Delete new jobs with a matching job in state 'processing'
1286 foreach my $macaddress (keys %{$hits}) {
1287 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1288 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1289 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1290 my $res = $job_db->exec_statement( $sql_statement );
1291 foreach my $hit (@{$res}) {
1292 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1293 }
1294 }
1295 }
1297 # Commit deletion
1298 $job_db->exec_statementlist(\@drops);
1300 # Look for new jobs that could be executed
1301 foreach my $macaddress (keys %{$hits}) {
1303 # Look if there is an executing job
1304 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1305 my $res = $job_db->exec_statement( $sql_statement );
1307 # Skip new jobs for host if there is a processing job
1308 if(defined($res) and defined @{$res}[0]) {
1309 next;
1310 }
1312 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1313 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1314 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1316 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1317 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1318 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1320 # expect macaddress is unique!!!!!!
1321 my $target = $res_hash->{1}->{hostname};
1323 # change header
1324 $job_msg =~ s/<header>job_/<header>gosa_/;
1326 # add sqlite_id
1327 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1329 $job_msg =~ /<header>(\S+)<\/header>/;
1330 my $header = $1 ;
1331 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1333 # update status in job queue to 'processing'
1334 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1335 my $res = $job_db->update_dbentry($sql_statement);
1337 # We don't want parallel processing
1338 last;
1339 }
1340 }
1342 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1343 }
1346 sub watch_for_new_messages {
1347 my ($kernel,$heap) = @_[KERNEL, HEAP];
1348 my @coll_user_msg; # collection list of outgoing messages
1350 # check messaging_db for new incoming messages with executable timestamp
1351 my $timestamp = &get_time();
1352 #my $sql_statement = "SELECT * FROM $messaging_tn WHERE (CAST (delivery_time AS INTEGER) + 120) < $timestamp";
1353 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1354 my $res = $messaging_db->exec_statement( $sql_statement );
1356 foreach my $hit (@{$res}) {
1358 # create outgoing messages
1359 my $message_to = @{$hit}[2];
1361 # translate message_to to plain login name
1362 my @reciever_l = ($message_to);
1363 my $message_id = @{$hit}[8];
1365 #add each outgoing msg to messaging_db
1366 my $reciever;
1367 foreach $reciever (@reciever_l) {
1368 my $sql_statement = "INSERT INTO $messaging_tn (subject, message_from, message_to, flag, direction, delivery_time, message, timestamp, id) ".
1369 "VALUES ('".
1370 @{$hit}[0]."', '". # subject
1371 @{$hit}[1]."', '". # message_from
1372 $reciever."', '". # message_to
1373 "none"."', '". # flag
1374 "out"."', '". # direction
1375 @{$hit}[5]."', '". # delivery_time
1376 @{$hit}[6]."', '". # message
1377 $timestamp."', '". # timestamp
1378 @{$hit}[8]. # id
1379 "')";
1380 &daemon_log("M DEBUG: $sql_statement", 1);
1381 my $res = $messaging_db->exec_statement($sql_statement);
1382 &daemon_log("M INFO: message '".@{$hit}[8]."' is prepared for delivery to reciever '$reciever'", 5);
1383 }
1385 # send outgoing messages
1386 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1387 my $res = $messaging_db->exec_statement( $sql_statement );
1388 foreach my $hit (@{$res}) {
1389 # add subject, from, to and message to list coll_user_msg
1390 my @user_msg = [@{$hit}[0], @{$hit}[1], $reciever, @{$hit}[6]];
1391 push( @coll_user_msg, \@user_msg);
1392 }
1394 # send outgoing list to myself (gosa-si-server) to deliver each message to user
1395 # reason for this workaround: if to much messages have to be delivered, it can come to
1396 # denial of service problems of the server. so, the incoming message list can be processed
1397 # by a forked child and gosa-si-server is always ready to work.
1398 my $collection_out_msg = &create_xml_hash("collection_user_messages", $server_address, $server_address);
1399 # add to hash 'msg1' => [subject, from, to, message]
1400 # hash to string
1401 # send msg to myself
1402 # TODO
1404 # set incoming message to flag d=deliverd
1405 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1406 &daemon_log("M DEBUG: $sql_statement", 7);
1407 $res = $messaging_db->update_dbentry($sql_statement);
1408 &daemon_log("M INFO: message '".@{$hit}[8]."' is set to flag 'p' (processed)", 5);
1410 }
1412 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1415 return;
1416 }
1419 sub watch_for_done_messages {
1420 my ($kernel,$heap) = @_[KERNEL, HEAP];
1422 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1423 return;
1424 }
1427 sub get_ldap_handle {
1428 my ($session_id) = @_;
1429 my $heap;
1430 my $ldap_handle;
1432 if (not defined $session_id ) { $session_id = 0 };
1433 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1435 if ($session_id == 0) {
1436 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1437 $ldap_handle = Net::LDAP->new( $ldap_uri );
1438 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1440 } else {
1441 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1442 if( defined $session_reference ) {
1443 $heap = $session_reference->get_heap();
1444 }
1446 if (not defined $heap) {
1447 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1448 return;
1449 }
1451 # TODO: This "if" is nonsense, because it doesn't prove that the
1452 # used handle is still valid - or if we've to reconnect...
1453 #if (not exists $heap->{ldap_handle}) {
1454 $ldap_handle = Net::LDAP->new( $ldap_uri );
1455 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1456 $heap->{ldap_handle} = $ldap_handle;
1457 #}
1458 }
1459 return $ldap_handle;
1460 }
1463 sub change_fai_state {
1464 my ($st, $targets, $session_id) = @_;
1465 $session_id = 0 if not defined $session_id;
1466 # Set FAI state to localboot
1467 my %mapActions= (
1468 reboot => '',
1469 update => 'softupdate',
1470 localboot => 'localboot',
1471 reinstall => 'install',
1472 rescan => '',
1473 wake => '',
1474 memcheck => 'memcheck',
1475 sysinfo => 'sysinfo',
1476 install => 'install',
1477 );
1479 # Return if this is unknown
1480 if (!exists $mapActions{ $st }){
1481 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1482 return;
1483 }
1485 my $state= $mapActions{ $st };
1487 my $ldap_handle = &get_ldap_handle($session_id);
1488 if( defined($ldap_handle) ) {
1490 # Build search filter for hosts
1491 my $search= "(&(objectClass=GOhard)";
1492 foreach (@{$targets}){
1493 $search.= "(macAddress=$_)";
1494 }
1495 $search.= ")";
1497 # If there's any host inside of the search string, procress them
1498 if (!($search =~ /macAddress/)){
1499 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1500 return;
1501 }
1503 # Perform search for Unit Tag
1504 my $mesg = $ldap_handle->search(
1505 base => $ldap_base,
1506 scope => 'sub',
1507 attrs => ['dn', 'FAIstate', 'objectClass'],
1508 filter => "$search"
1509 );
1511 if ($mesg->count) {
1512 my @entries = $mesg->entries;
1513 foreach my $entry (@entries) {
1514 # Only modify entry if it is not set to '$state'
1515 if ($entry->get_value("FAIstate") ne "$state"){
1516 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1517 my $result;
1518 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1519 if (exists $tmp{'FAIobject'}){
1520 if ($state eq ''){
1521 $result= $ldap_handle->modify($entry->dn, changes => [
1522 delete => [ FAIstate => [] ] ]);
1523 } else {
1524 $result= $ldap_handle->modify($entry->dn, changes => [
1525 replace => [ FAIstate => $state ] ]);
1526 }
1527 } elsif ($state ne ''){
1528 $result= $ldap_handle->modify($entry->dn, changes => [
1529 add => [ objectClass => 'FAIobject' ],
1530 add => [ FAIstate => $state ] ]);
1531 }
1533 # Errors?
1534 if ($result->code){
1535 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1536 }
1537 } else {
1538 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1539 }
1540 }
1541 }
1542 # if no ldap handle defined
1543 } else {
1544 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1545 }
1547 }
1550 sub change_goto_state {
1551 my ($st, $targets, $session_id) = @_;
1552 $session_id = 0 if not defined $session_id;
1554 # Switch on or off?
1555 my $state= $st eq 'active' ? 'active': 'locked';
1557 my $ldap_handle = &get_ldap_handle($session_id);
1558 if( defined($ldap_handle) ) {
1560 # Build search filter for hosts
1561 my $search= "(&(objectClass=GOhard)";
1562 foreach (@{$targets}){
1563 $search.= "(macAddress=$_)";
1564 }
1565 $search.= ")";
1567 # If there's any host inside of the search string, procress them
1568 if (!($search =~ /macAddress/)){
1569 return;
1570 }
1572 # Perform search for Unit Tag
1573 my $mesg = $ldap_handle->search(
1574 base => $ldap_base,
1575 scope => 'sub',
1576 attrs => ['dn', 'gotoMode'],
1577 filter => "$search"
1578 );
1580 if ($mesg->count) {
1581 my @entries = $mesg->entries;
1582 foreach my $entry (@entries) {
1584 # Only modify entry if it is not set to '$state'
1585 if ($entry->get_value("gotoMode") ne $state){
1587 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1588 my $result;
1589 $result= $ldap_handle->modify($entry->dn, changes => [
1590 replace => [ gotoMode => $state ] ]);
1592 # Errors?
1593 if ($result->code){
1594 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1595 }
1597 }
1598 }
1599 }
1601 }
1602 }
1605 sub run_create_fai_server_db {
1606 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1607 my $session_id = $session->ID;
1608 my $task = POE::Wheel::Run->new(
1609 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1610 StdoutEvent => "session_run_result",
1611 StderrEvent => "session_run_debug",
1612 CloseEvent => "session_run_done",
1613 );
1615 $heap->{task}->{ $task->ID } = $task;
1616 return;
1617 }
1620 sub create_fai_server_db {
1621 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1622 my $result;
1624 if (not defined $session_id) { $session_id = 0; }
1625 my $ldap_handle = &get_ldap_handle();
1626 if(defined($ldap_handle)) {
1627 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1628 my $mesg= $ldap_handle->search(
1629 base => $ldap_base,
1630 scope => 'sub',
1631 attrs => ['FAIrepository', 'gosaUnitTag'],
1632 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1633 );
1634 if($mesg->{'resultCode'} == 0 &&
1635 $mesg->count != 0) {
1636 foreach my $entry (@{$mesg->{entries}}) {
1637 if($entry->exists('FAIrepository')) {
1638 # Add an entry for each Repository configured for server
1639 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1640 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1641 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1642 $result= $fai_server_db->add_dbentry( {
1643 table => $table_name,
1644 primkey => ['server', 'release', 'tag'],
1645 server => $tmp_url,
1646 release => $tmp_release,
1647 sections => $tmp_sections,
1648 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1649 } );
1650 }
1651 }
1652 }
1653 }
1654 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1656 # TODO: Find a way to post the 'create_packages_list_db' event
1657 if(not defined($dont_create_packages_list)) {
1658 #&create_packages_list_db;
1660 # it should not be possible to trigger a recreation of packages_list_db
1661 # while packages_list_db is under construction, so set flag packages_list_under_construction
1662 # which is tested befor recreation can be started
1663 &create_packages_list_db(undef, undef, $session_id);
1664 }
1665 }
1667 $ldap_handle->disconnect;
1668 return $result;
1669 }
1672 sub run_create_fai_release_db {
1673 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1674 my $session_id = $session->ID;
1675 my $task = POE::Wheel::Run->new(
1676 Program => sub { &create_fai_release_db($table_name, $session_id) },
1677 StdoutEvent => "session_run_result",
1678 StderrEvent => "session_run_debug",
1679 CloseEvent => "session_run_done",
1680 );
1682 $heap->{task}->{ $task->ID } = $task;
1683 return;
1684 }
1687 sub create_fai_release_db {
1688 my ($table_name, $session_id) = @_;
1689 my $result;
1691 # used for logging
1692 if (not defined $session_id) { $session_id = 0; }
1694 my $ldap_handle = &get_ldap_handle();
1695 if(defined($ldap_handle)) {
1696 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1697 my $mesg= $ldap_handle->search(
1698 base => $ldap_base,
1699 scope => 'sub',
1700 attrs => [],
1701 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1702 );
1703 if($mesg->{'resultCode'} == 0 &&
1704 $mesg->count != 0) {
1705 # Walk through all possible FAI container ou's
1706 my @sql_list;
1707 my $timestamp= &get_time();
1708 foreach my $ou (@{$mesg->{entries}}) {
1709 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1710 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1711 my @tmp_array=get_fai_release_entries($tmp_classes);
1712 if(@tmp_array) {
1713 foreach my $entry (@tmp_array) {
1714 if(defined($entry) && ref($entry) eq 'HASH') {
1715 my $sql=
1716 "INSERT INTO $table_name "
1717 ."(timestamp, release, class, type, state) VALUES ("
1718 .$timestamp.","
1719 ."'".$entry->{'release'}."',"
1720 ."'".$entry->{'class'}."',"
1721 ."'".$entry->{'type'}."',"
1722 ."'".$entry->{'state'}."')";
1723 push @sql_list, $sql;
1724 }
1725 }
1726 }
1727 }
1728 }
1730 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1731 if(@sql_list) {
1732 unshift @sql_list, "DELETE FROM $table_name";
1733 $fai_release_db->exec_statementlist(\@sql_list);
1734 }
1735 daemon_log("$session_id DEBUG: Done with inserting",6);
1736 }
1737 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1738 }
1739 $ldap_handle->disconnect;
1740 return $result;
1741 }
1743 sub get_fai_types {
1744 my $tmp_classes = shift || return undef;
1745 my @result;
1747 foreach my $type(keys %{$tmp_classes}) {
1748 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1749 my $entry = {
1750 type => $type,
1751 state => $tmp_classes->{$type}[0],
1752 };
1753 push @result, $entry;
1754 }
1755 }
1757 return @result;
1758 }
1760 sub get_fai_state {
1761 my $result = "";
1762 my $tmp_classes = shift || return $result;
1764 foreach my $type(keys %{$tmp_classes}) {
1765 if(defined($tmp_classes->{$type}[0])) {
1766 $result = $tmp_classes->{$type}[0];
1768 # State is equal for all types in class
1769 last;
1770 }
1771 }
1773 return $result;
1774 }
1776 sub resolve_fai_classes {
1777 my ($fai_base, $ldap_handle) = @_;
1778 my $result;
1779 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1780 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1781 my $fai_classes;
1783 daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1784 my $mesg= $ldap_handle->search(
1785 base => $fai_base,
1786 scope => 'sub',
1787 attrs => ['cn','objectClass','FAIstate'],
1788 filter => $fai_filter,
1789 );
1790 daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1792 if($mesg->{'resultCode'} == 0 &&
1793 $mesg->count != 0) {
1794 foreach my $entry (@{$mesg->{entries}}) {
1795 if($entry->exists('cn')) {
1796 my $tmp_dn= $entry->dn();
1798 # Skip classname and ou dn parts for class
1799 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1801 # Skip classes without releases
1802 if((!defined($tmp_release)) || length($tmp_release)==0) {
1803 next;
1804 }
1806 my $tmp_cn= $entry->get_value('cn');
1807 my $tmp_state= $entry->get_value('FAIstate');
1809 my $tmp_type;
1810 # Get FAI type
1811 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1812 if(grep $_ eq $oclass, @possible_fai_classes) {
1813 $tmp_type= $oclass;
1814 last;
1815 }
1816 }
1818 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1819 # A Subrelease
1820 my @sub_releases = split(/,/, $tmp_release);
1822 # Walk through subreleases and build hash tree
1823 my $hash;
1824 while(my $tmp_sub_release = pop @sub_releases) {
1825 $hash .= "\{'$tmp_sub_release'\}->";
1826 }
1827 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1828 } else {
1829 # A branch, no subrelease
1830 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1831 }
1832 } elsif (!$entry->exists('cn')) {
1833 my $tmp_dn= $entry->dn();
1834 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1836 # Skip classes without releases
1837 if((!defined($tmp_release)) || length($tmp_release)==0) {
1838 next;
1839 }
1841 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1842 # A Subrelease
1843 my @sub_releases= split(/,/, $tmp_release);
1845 # Walk through subreleases and build hash tree
1846 my $hash;
1847 while(my $tmp_sub_release = pop @sub_releases) {
1848 $hash .= "\{'$tmp_sub_release'\}->";
1849 }
1850 # Remove the last two characters
1851 chop($hash);
1852 chop($hash);
1854 eval('$fai_classes->'.$hash.'= {}');
1855 } else {
1856 # A branch, no subrelease
1857 if(!exists($fai_classes->{$tmp_release})) {
1858 $fai_classes->{$tmp_release} = {};
1859 }
1860 }
1861 }
1862 }
1864 # The hash is complete, now we can honor the copy-on-write based missing entries
1865 foreach my $release (keys %$fai_classes) {
1866 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1867 }
1868 }
1869 return $result;
1870 }
1872 sub apply_fai_inheritance {
1873 my $fai_classes = shift || return {};
1874 my $tmp_classes;
1876 # Get the classes from the branch
1877 foreach my $class (keys %{$fai_classes}) {
1878 # Skip subreleases
1879 if($class =~ /^ou=.*$/) {
1880 next;
1881 } else {
1882 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1883 }
1884 }
1886 # Apply to each subrelease
1887 foreach my $subrelease (keys %{$fai_classes}) {
1888 if($subrelease =~ /ou=/) {
1889 foreach my $tmp_class (keys %{$tmp_classes}) {
1890 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1891 $fai_classes->{$subrelease}->{$tmp_class} =
1892 deep_copy($tmp_classes->{$tmp_class});
1893 } else {
1894 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1895 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1896 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1897 deep_copy($tmp_classes->{$tmp_class}->{$type});
1898 }
1899 }
1900 }
1901 }
1902 }
1903 }
1905 # Find subreleases in deeper levels
1906 foreach my $subrelease (keys %{$fai_classes}) {
1907 if($subrelease =~ /ou=/) {
1908 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1909 if($subsubrelease =~ /ou=/) {
1910 apply_fai_inheritance($fai_classes->{$subrelease});
1911 }
1912 }
1913 }
1914 }
1916 return $fai_classes;
1917 }
1919 sub get_fai_release_entries {
1920 my $tmp_classes = shift || return;
1921 my $parent = shift || "";
1922 my @result = shift || ();
1924 foreach my $entry (keys %{$tmp_classes}) {
1925 if(defined($entry)) {
1926 if($entry =~ /^ou=.*$/) {
1927 my $release_name = $entry;
1928 $release_name =~ s/ou=//g;
1929 if(length($parent)>0) {
1930 $release_name = $parent."/".$release_name;
1931 }
1932 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1933 foreach my $bufentry(@bufentries) {
1934 push @result, $bufentry;
1935 }
1936 } else {
1937 my @types = get_fai_types($tmp_classes->{$entry});
1938 foreach my $type (@types) {
1939 push @result,
1940 {
1941 'class' => $entry,
1942 'type' => $type->{'type'},
1943 'release' => $parent,
1944 'state' => $type->{'state'},
1945 };
1946 }
1947 }
1948 }
1949 }
1951 return @result;
1952 }
1954 sub deep_copy {
1955 my $this = shift;
1956 if (not ref $this) {
1957 $this;
1958 } elsif (ref $this eq "ARRAY") {
1959 [map deep_copy($_), @$this];
1960 } elsif (ref $this eq "HASH") {
1961 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1962 } else { die "what type is $_?" }
1963 }
1966 sub session_run_result {
1967 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1968 $kernel->sig(CHLD => "child_reap");
1969 }
1971 sub session_run_debug {
1972 my $result = $_[ARG0];
1973 print STDERR "$result\n";
1974 }
1976 sub session_run_done {
1977 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1978 delete $heap->{task}->{$task_id};
1979 }
1981 sub create_sources_list {
1982 my $ldap_handle = &get_ldap_handle;
1983 my $result="/tmp/gosa_si_tmp_sources_list";
1985 # Remove old file
1986 if(stat($result)) {
1987 unlink($result);
1988 }
1990 my $fh;
1991 open($fh, ">$result") or return undef;
1992 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1993 my $mesg=$ldap_handle->search(
1994 base => $ldap_server_dn,
1995 scope => 'base',
1996 attrs => 'FAIrepository',
1997 filter => 'objectClass=FAIrepositoryServer'
1998 );
1999 if($mesg->count) {
2000 foreach my $entry(@{$mesg->{'entries'}}) {
2001 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2002 my ($server, $tag, $release, $sections)= split /\|/, $value;
2003 my $line = "deb $server $release";
2004 $sections =~ s/,/ /g;
2005 $line.= " $sections";
2006 print $fh $line."\n";
2007 }
2008 }
2009 }
2010 }
2011 close($fh);
2013 return $result;
2014 }
2017 sub run_create_packages_list_db {
2018 my ($session, $heap) = @_[SESSION, HEAP];
2019 my $session_id = $session->ID;
2021 my $task = POE::Wheel::Run->new(
2022 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2023 StdoutEvent => "session_run_result",
2024 StderrEvent => "session_run_debug",
2025 CloseEvent => "session_run_done",
2026 );
2027 $heap->{task}->{ $task->ID } = $task;
2028 }
2031 sub create_packages_list_db {
2032 my ($ldap_handle, $sources_file, $session_id);
2034 if ($packages_list_under_construction) {
2035 daemon_log("#########################################################################################\n\n\n\n\n");
2036 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait untill this process is finished", 3);
2037 return;
2038 }
2039 # set packages_list_under_construction to true
2040 $packages_list_under_construction = 1;
2042 if (not defined $session_id) { $session_id = 0; }
2043 if (not defined $ldap_handle) {
2044 $ldap_handle= &get_ldap_handle();
2046 if (not defined $ldap_handle) {
2047 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2048 return;
2049 }
2050 }
2051 if (not defined $sources_file) {
2052 $sources_file = &create_sources_list;
2053 }
2055 my $line;
2056 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2058 open(CONFIG, "<$sources_file") or do {
2059 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2060 return;
2061 };
2063 # Read lines
2064 while ($line = <CONFIG>){
2065 # Unify
2066 chop($line);
2067 $line =~ s/^\s+//;
2068 $line =~ s/^\s+/ /;
2070 # Strip comments
2071 $line =~ s/#.*$//g;
2073 # Skip empty lines
2074 if ($line =~ /^\s*$/){
2075 next;
2076 }
2078 # Interpret deb line
2079 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2080 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2081 my $section;
2082 foreach $section (split(' ', $sections)){
2083 &parse_package_info( $baseurl, $dist, $section, $session_id );
2084 }
2085 }
2086 }
2088 close (CONFIG);
2090 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2091 # set packages_list_under_construction to false
2092 $packages_list_under_construction = 0;
2094 return;
2095 }
2098 sub parse_package_info {
2099 my ($baseurl, $dist, $section, $session_id)= @_;
2100 my ($package);
2101 if (not defined $session_id) { $session_id = 0; }
2102 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2103 $repo_dirs{ "${repo_path}/pool" } = 1;
2105 foreach $package ("Packages.gz"){
2106 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2107 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2108 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2109 }
2110 find(\&cleanup_and_extract, keys( %repo_dirs ));
2111 }
2114 sub get_package {
2115 my ($url, $dest, $session_id)= @_;
2116 if (not defined $session_id) { $session_id = 0; }
2118 my $tpath = dirname($dest);
2119 -d "$tpath" || mkpath "$tpath";
2121 # This is ugly, but I've no time to take a look at "how it works in perl"
2122 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2123 system("gunzip -cd '$dest' > '$dest.in'");
2124 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2125 # unlink($dest);
2126 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2127 } else {
2128 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2129 }
2130 return 0;
2131 }
2133 sub parse_package {
2134 my ($path, $dist, $srv_path, $session_id)= @_;
2135 if (not defined $session_id) { $session_id = 0;}
2136 my ($package, $version, $section, $description);
2137 my @sql_list;
2138 my $PACKAGES;
2140 if(not stat("$path.in")) {
2141 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2142 return;
2143 }
2145 open($PACKAGES, "<$path.in");
2146 if(not defined($PACKAGES)) {
2147 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2148 return;
2149 }
2151 # Read lines
2152 while (<$PACKAGES>){
2153 my $line = $_;
2154 # Unify
2155 chop($line);
2157 # Use empty lines as a trigger
2158 if ($line =~ /^\s*$/){
2159 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2160 push(@sql_list, $sql);
2161 $package = "none";
2162 $version = "none";
2163 $section = "none";
2164 $description = "none";
2165 next;
2166 }
2168 # Trigger for package name
2169 if ($line =~ /^Package:\s/){
2170 ($package)= ($line =~ /^Package: (.*)$/);
2171 next;
2172 }
2174 # Trigger for version
2175 if ($line =~ /^Version:\s/){
2176 ($version)= ($line =~ /^Version: (.*)$/);
2177 next;
2178 }
2180 # Trigger for description
2181 if ($line =~ /^Description:\s/){
2182 ($description)= ($line =~ /^Description: (.*)$/);
2183 next;
2184 }
2186 # Trigger for section
2187 if ($line =~ /^Section:\s/){
2188 ($section)= ($line =~ /^Section: (.*)$/);
2189 next;
2190 }
2192 # Trigger for filename
2193 if ($line =~ /^Filename:\s/){
2194 my ($filename) = ($line =~ /^Filename: (.*)$/);
2195 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2196 next;
2197 }
2198 }
2200 close( $PACKAGES );
2201 # unlink( "$path.in" );
2203 $packages_list_db->exec_statementlist(\@sql_list);
2204 }
2206 sub store_fileinfo {
2207 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2209 my %fileinfo = (
2210 'package' => $package,
2211 'dist' => $dist,
2212 'version' => $vers,
2213 );
2215 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2216 }
2218 sub cleanup_and_extract {
2219 my ($session_id) = @_;
2220 if (not defined $session_id) { $session_id = 0; }
2221 my $fileinfo = $repo_files{ $File::Find::name };
2223 if( defined $fileinfo ) {
2225 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2226 my $sql;
2227 my $package = $fileinfo->{ 'package' };
2228 my $newver = $fileinfo->{ 'version' };
2230 mkpath($dir);
2231 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2233 if( -f "$dir/DEBIAN/templates" ) {
2235 daemon_log("$session_id DEBUG: Found debconf templates in '$package' - $newver", 5);
2237 my $tmpl= "";
2238 {
2239 local $/=undef;
2240 open FILE, "$dir/DEBIAN/templates";
2241 $tmpl = &encode_base64(<FILE>);
2242 close FILE;
2243 }
2244 rmtree("$dir/DEBIAN/templates");
2246 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2248 } else {
2249 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2250 }
2252 my $res= $main::packages_list_db->update_dbentry($sql);
2253 }
2254 }
2257 #==== MAIN = main ==============================================================
2258 # parse commandline options
2259 Getopt::Long::Configure( "bundling" );
2260 GetOptions("h|help" => \&usage,
2261 "c|config=s" => \$cfg_file,
2262 "f|foreground" => \$foreground,
2263 "v|verbose+" => \$verbose,
2264 "no-bus+" => \$no_bus,
2265 "no-arp+" => \$no_arp,
2266 );
2268 # read and set config parameters
2269 &check_cmdline_param ;
2270 &read_configfile;
2271 &check_pid;
2273 $SIG{CHLD} = 'IGNORE';
2275 # forward error messages to logfile
2276 if( ! $foreground ) {
2277 open( STDIN, '+>/dev/null' );
2278 open( STDOUT, '+>&STDIN' );
2279 open( STDERR, '+>&STDIN' );
2280 }
2282 # Just fork, if we are not in foreground mode
2283 if( ! $foreground ) {
2284 chdir '/' or die "Can't chdir to /: $!";
2285 $pid = fork;
2286 setsid or die "Can't start a new session: $!";
2287 umask 0;
2288 } else {
2289 $pid = $$;
2290 }
2292 # Do something useful - put our PID into the pid_file
2293 if( 0 != $pid ) {
2294 open( LOCK_FILE, ">$pid_file" );
2295 print LOCK_FILE "$pid\n";
2296 close( LOCK_FILE );
2297 if( !$foreground ) {
2298 exit( 0 )
2299 };
2300 }
2302 daemon_log(" ", 1);
2303 daemon_log("$0 started!", 1);
2305 if ($no_bus > 0) {
2306 $bus_activ = "false"
2307 }
2309 # connect to gosa-si job queue
2310 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2311 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2313 # connect to known_clients_db
2314 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2315 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2317 # connect to known_server_db
2318 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2319 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2321 # connect to login_usr_db
2322 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2323 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2325 # connect to fai_server_db and fai_release_db
2326 unlink($fai_server_file_name);
2327 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2328 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2330 unlink($fai_release_file_name);
2331 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2332 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2334 # connect to packages_list_db
2335 unlink($packages_list_file_name);
2336 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2337 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2339 # connect to messaging_db
2340 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2341 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2344 # create xml object used for en/decrypting
2345 $xml = new XML::Simple();
2347 # create socket for incoming xml messages
2349 POE::Component::Server::TCP->new(
2350 Port => $server_port,
2351 ClientInput => sub {
2352 my ($kernel, $input) = @_[KERNEL, ARG0];
2353 push(@tasks, $input);
2354 $kernel->yield("next_task");
2355 },
2356 InlineStates => {
2357 next_task => \&next_task,
2358 task_result => \&handle_task_result,
2359 task_done => \&handle_task_done,
2360 task_debug => \&handle_task_debug,
2361 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2362 }
2363 );
2365 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2367 # create session for repeatedly checking the job queue for jobs
2368 POE::Session->create(
2369 inline_states => {
2370 _start => \&_start,
2371 sig_handler => \&sig_handler,
2372 watch_for_new_messages => \&watch_for_new_messages,
2373 watch_for_done_messages => \&watch_for_done_messages,
2374 watch_for_new_jobs => \&watch_for_new_jobs,
2375 watch_for_done_jobs => \&watch_for_done_jobs,
2376 create_packages_list_db => \&run_create_packages_list_db,
2377 create_fai_server_db => \&run_create_fai_server_db,
2378 create_fai_release_db => \&run_create_fai_release_db,
2379 session_run_result => \&session_run_result,
2380 session_run_debug => \&session_run_debug,
2381 session_run_done => \&session_run_done,
2382 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2383 }
2384 );
2387 # import all modules
2388 &import_modules;
2390 # check wether all modules are gosa-si valid passwd check
2392 POE::Kernel->run();
2393 exit;