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);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
70 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($pid_file, $procid, $pid, $log_file);
76 my ($arp_activ, $arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $bus_address;
95 our $gosa_address;
96 our $no_bus;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
104 # specifies the verbosity of the daemon_log
105 $verbose = 0 ;
107 # if foreground is not null, script will be not forked to background
108 $foreground = 0 ;
110 # specifies the timeout seconds while checking the online status of a registrating client
111 $ping_timeout = 5;
113 $no_bus = 0;
114 $bus_activ = "true";
115 $no_arp = 0;
116 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
117 my @packages_list_statements;
118 my $watch_for_new_jobs_in_progress = 0;
120 # holds all incoming decrypted messages
121 our $incoming_db;
122 our $incoming_tn = 'incoming';
123 my $incoming_file_name;
124 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
125 "timestamp DEFAULT 'none'",
126 "headertag DEFAULT 'none'",
127 "targettag DEFAULT 'none'",
128 "xmlmessage DEFAULT 'none'",
129 "module DEFAULT 'none'",
130 );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
137 "timestamp DEFAULT 'none'",
138 "status DEFAULT 'none'",
139 "result DEFAULT 'none'",
140 "progress DEFAULT 'none'",
141 "headertag DEFAULT 'none'",
142 "targettag DEFAULT 'none'",
143 "xmlmessage DEFAULT 'none'",
144 "macaddress DEFAULT 'none'",
145 "plainname DEFAULT 'none'",
146 );
148 # holds all other gosa-sd as well as the gosa-sd-bus
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients";
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server";
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release";
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386";
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging";
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
195 "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai";
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 %cfg_defaults = (
208 "general" => {
209 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
210 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
211 },
212 "bus" => {
213 "activ" => [\$bus_activ, "true"],
214 },
215 "server" => {
216 "port" => [\$server_port, "20081"],
217 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
218 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
219 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
220 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
221 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
222 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
223 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
224 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
225 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
226 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
227 "repo-path" => [\$repo_path, '/srv/www/repository'],
228 "ldap-uri" => [\$ldap_uri, ""],
229 "ldap-base" => [\$ldap_base, ""],
230 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
231 "ldap-admin-password" => [\$ldap_admin_password, ""],
232 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
233 "max-clients" => [\$max_clients, 10],
234 },
235 "GOsaPackages" => {
236 "ip" => [\$gosa_ip, "0.0.0.0"],
237 "port" => [\$gosa_port, "20082"],
238 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
239 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
240 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
241 "key" => [\$GosaPackages_key, "none"],
242 },
243 "ClientPackages" => {
244 "key" => [\$ClientPackages_key, "none"],
245 },
246 "ServerPackages"=> {
247 "address" => [\$foreign_server_string, ""],
248 "domain" => [\$server_domain, ""],
249 "key" => [\$ServerPackages_key, "none"],
250 "key-lifetime" => [\$foreign_servers_register_delay, 120],
251 }
252 );
255 #=== FUNCTION ================================================================
256 # NAME: usage
257 # PARAMETERS: nothing
258 # RETURNS: nothing
259 # DESCRIPTION: print out usage text to STDERR
260 #===============================================================================
261 sub usage {
262 print STDERR << "EOF" ;
263 usage: $prg [-hvf] [-c config]
265 -h : this (help) message
266 -c <file> : config file
267 -f : foreground, process will not be forked to background
268 -v : be verbose (multiple to increase verbosity)
269 -no-bus : starts $prg without connection to bus
270 -no-arp : starts $prg without connection to arp module
272 EOF
273 print "\n" ;
274 }
277 #=== FUNCTION ================================================================
278 # NAME: read_configfile
279 # PARAMETERS: cfg_file - string -
280 # RETURNS: nothing
281 # DESCRIPTION: read cfg_file and set variables
282 #===============================================================================
283 sub read_configfile {
284 my $cfg;
285 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
286 if( -r $cfg_file ) {
287 $cfg = Config::IniFiles->new( -file => $cfg_file );
288 } else {
289 print STDERR "Couldn't read config file!\n";
290 }
291 } else {
292 $cfg = Config::IniFiles->new() ;
293 }
294 foreach my $section (keys %cfg_defaults) {
295 foreach my $param (keys %{$cfg_defaults{ $section }}) {
296 my $pinfo = $cfg_defaults{ $section }{ $param };
297 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
298 }
299 }
300 }
303 #=== FUNCTION ================================================================
304 # NAME: logging
305 # PARAMETERS: level - string - default 'info'
306 # msg - string -
307 # facility - string - default 'LOG_DAEMON'
308 # RETURNS: nothing
309 # DESCRIPTION: function for logging
310 #===============================================================================
311 sub daemon_log {
312 # log into log_file
313 my( $msg, $level ) = @_;
314 if(not defined $msg) { return }
315 if(not defined $level) { $level = 1 }
316 if(defined $log_file){
317 open(LOG_HANDLE, ">>$log_file");
318 chmod 0600, $log_file;
319 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
320 print STDERR "cannot open $log_file: $!";
321 return }
322 chomp($msg);
323 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
324 if($level <= $verbose){
325 my ($seconds, $minutes, $hours, $monthday, $month,
326 $year, $weekday, $yearday, $sommertime) = localtime(time);
327 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
328 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
329 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
330 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
331 $month = $monthnames[$month];
332 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
333 $year+=1900;
334 my $name = $prg;
336 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
337 print LOG_HANDLE $log_msg;
338 if( $foreground ) {
339 print STDERR $log_msg;
340 }
341 }
342 close( LOG_HANDLE );
343 }
344 }
347 #=== FUNCTION ================================================================
348 # NAME: check_cmdline_param
349 # PARAMETERS: nothing
350 # RETURNS: nothing
351 # DESCRIPTION: validates commandline parameter
352 #===============================================================================
353 sub check_cmdline_param () {
354 my $err_config;
355 my $err_counter = 0;
356 if(not defined($cfg_file)) {
357 $cfg_file = "/etc/gosa-si/server.conf";
358 if(! -r $cfg_file) {
359 $err_config = "please specify a config file";
360 $err_counter += 1;
361 }
362 }
363 if( $err_counter > 0 ) {
364 &usage( "", 1 );
365 if( defined( $err_config)) { print STDERR "$err_config\n"}
366 print STDERR "\n";
367 exit( -1 );
368 }
369 }
372 #=== FUNCTION ================================================================
373 # NAME: check_pid
374 # PARAMETERS: nothing
375 # RETURNS: nothing
376 # DESCRIPTION: handels pid processing
377 #===============================================================================
378 sub check_pid {
379 $pid = -1;
380 # Check, if we are already running
381 if( open(LOCK_FILE, "<$pid_file") ) {
382 $pid = <LOCK_FILE>;
383 if( defined $pid ) {
384 chomp( $pid );
385 if( -f "/proc/$pid/stat" ) {
386 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
387 if( $stat ) {
388 daemon_log("ERROR: Already running",1);
389 close( LOCK_FILE );
390 exit -1;
391 }
392 }
393 }
394 close( LOCK_FILE );
395 unlink( $pid_file );
396 }
398 # create a syslog msg if it is not to possible to open PID file
399 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
400 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
401 if (open(LOCK_FILE, '<', $pid_file)
402 && ($pid = <LOCK_FILE>))
403 {
404 chomp($pid);
405 $msg .= "(PID $pid)\n";
406 } else {
407 $msg .= "(unable to read PID)\n";
408 }
409 if( ! ($foreground) ) {
410 openlog( $0, "cons,pid", "daemon" );
411 syslog( "warning", $msg );
412 closelog();
413 }
414 else {
415 print( STDERR " $msg " );
416 }
417 exit( -1 );
418 }
419 }
421 #=== FUNCTION ================================================================
422 # NAME: import_modules
423 # PARAMETERS: module_path - string - abs. path to the directory the modules
424 # are stored
425 # RETURNS: nothing
426 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
427 # state is on is imported by "require 'file';"
428 #===============================================================================
429 sub import_modules {
430 daemon_log(" ", 1);
432 if (not -e $modules_path) {
433 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
434 }
436 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
437 while (defined (my $file = readdir (DIR))) {
438 if (not $file =~ /(\S*?).pm$/) {
439 next;
440 }
441 my $mod_name = $1;
443 if( $file =~ /ArpHandler.pm/ ) {
444 if( $no_arp > 0 ) {
445 next;
446 }
447 }
449 eval { require $file; };
450 if ($@) {
451 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
452 daemon_log("$@", 5);
453 } else {
454 my $info = eval($mod_name.'::get_module_info()');
455 # Only load module if get_module_info() returns a non-null object
456 if( $info ) {
457 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
458 $known_modules->{$mod_name} = $info;
459 daemon_log("0 INFO: module $mod_name loaded", 5);
460 }
461 }
462 }
463 close (DIR);
464 }
467 #=== FUNCTION ================================================================
468 # NAME: sig_int_handler
469 # PARAMETERS: signal - string - signal arose from system
470 # RETURNS: noting
471 # DESCRIPTION: handels tasks to be done befor signal becomes active
472 #===============================================================================
473 sub sig_int_handler {
474 my ($signal) = @_;
476 # if (defined($ldap_handle)) {
477 # $ldap_handle->disconnect;
478 # }
479 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
482 daemon_log("shutting down gosa-si-server", 1);
483 system("kill `ps -C gosa-si-server-nobus -o pid=`");
484 }
485 $SIG{INT} = \&sig_int_handler;
488 sub check_key_and_xml_validity {
489 my ($crypted_msg, $module_key, $session_id) = @_;
490 my $msg;
491 my $msg_hash;
492 my $error_string;
493 eval{
494 $msg = &decrypt_msg($crypted_msg, $module_key);
496 if ($msg =~ /<xml>/i){
497 $msg =~ s/\s+/ /g; # just for better daemon_log
498 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
499 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
501 ##############
502 # check header
503 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
504 my $header_l = $msg_hash->{'header'};
505 if( 1 > @{$header_l} ) { die 'empty header tag'; }
506 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
507 my $header = @{$header_l}[0];
508 if( 0 == length $header) { die 'empty string in header tag'; }
510 ##############
511 # check source
512 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
513 my $source_l = $msg_hash->{'source'};
514 if( 1 > @{$source_l} ) { die 'empty source tag'; }
515 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
516 my $source = @{$source_l}[0];
517 if( 0 == length $source) { die 'source error'; }
519 ##############
520 # check target
521 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
522 my $target_l = $msg_hash->{'target'};
523 if( 1 > @{$target_l} ) { die 'empty target tag'; }
524 }
525 };
526 if($@) {
527 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
528 $msg = undef;
529 $msg_hash = undef;
530 }
532 return ($msg, $msg_hash);
533 }
536 sub check_outgoing_xml_validity {
537 my ($msg) = @_;
539 my $msg_hash;
540 eval{
541 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
543 ##############
544 # check header
545 my $header_l = $msg_hash->{'header'};
546 if( 1 != @{$header_l} ) {
547 die 'no or more than one headers specified';
548 }
549 my $header = @{$header_l}[0];
550 if( 0 == length $header) {
551 die 'header has length 0';
552 }
554 ##############
555 # check source
556 my $source_l = $msg_hash->{'source'};
557 if( 1 != @{$source_l} ) {
558 die 'no or more than 1 sources specified';
559 }
560 my $source = @{$source_l}[0];
561 if( 0 == length $source) {
562 die 'source has length 0';
563 }
564 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
565 $source =~ /^GOSA$/i ) {
566 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
567 }
569 ##############
570 # check target
571 my $target_l = $msg_hash->{'target'};
572 if( 0 == @{$target_l} ) {
573 die "no targets specified";
574 }
575 foreach my $target (@$target_l) {
576 if( 0 == length $target) {
577 die "target has length 0";
578 }
579 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
580 $target =~ /^GOSA$/i ||
581 $target =~ /^\*$/ ||
582 $target =~ /KNOWN_SERVER/i ||
583 $target =~ /JOBDB/i ||
584 $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 ){
585 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
586 }
587 }
588 };
589 if($@) {
590 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
591 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
592 $msg_hash = undef;
593 }
595 return ($msg_hash);
596 }
599 sub input_from_known_server {
600 my ($input, $remote_ip, $session_id) = @_ ;
601 my ($msg, $msg_hash, $module);
603 my $sql_statement= "SELECT * FROM known_server";
604 my $query_res = $known_server_db->select_dbentry( $sql_statement );
606 while( my ($hit_num, $hit) = each %{ $query_res } ) {
607 my $host_name = $hit->{hostname};
608 if( not $host_name =~ "^$remote_ip") {
609 next;
610 }
611 my $host_key = $hit->{hostkey};
612 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
613 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
615 # check if module can open msg envelope with module key
616 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
617 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
618 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
619 daemon_log("$@", 8);
620 next;
621 }
622 else {
623 $msg = $tmp_msg;
624 $msg_hash = $tmp_msg_hash;
625 $module = "ServerPackages";
626 last;
627 }
628 }
630 if( (!$msg) || (!$msg_hash) || (!$module) ) {
631 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
632 }
634 return ($msg, $msg_hash, $module);
635 }
638 sub input_from_known_client {
639 my ($input, $remote_ip, $session_id) = @_ ;
640 my ($msg, $msg_hash, $module);
642 my $sql_statement= "SELECT * FROM known_clients";
643 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
644 while( my ($hit_num, $hit) = each %{ $query_res } ) {
645 my $host_name = $hit->{hostname};
646 if( not $host_name =~ /^$remote_ip:\d*$/) {
647 next;
648 }
649 my $host_key = $hit->{hostkey};
650 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
651 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
653 # check if module can open msg envelope with module key
654 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
656 if( (!$msg) || (!$msg_hash) ) {
657 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
658 &daemon_log("$@", 8);
659 next;
660 }
661 else {
662 $module = "ClientPackages";
663 last;
664 }
665 }
667 if( (!$msg) || (!$msg_hash) || (!$module) ) {
668 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
669 }
671 return ($msg, $msg_hash, $module);
672 }
675 sub input_from_unknown_host {
676 no strict "refs";
677 my ($input, $session_id) = @_ ;
678 my ($msg, $msg_hash, $module);
679 my $error_string;
681 my %act_modules = %$known_modules;
683 while( my ($mod, $info) = each(%act_modules)) {
685 # check a key exists for this module
686 my $module_key = ${$mod."_key"};
687 if( not defined $module_key ) {
688 if( $mod eq 'ArpHandler' ) {
689 next;
690 }
691 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
692 next;
693 }
694 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
696 # check if module can open msg envelope with module key
697 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
698 if( (not defined $msg) || (not defined $msg_hash) ) {
699 next;
700 }
701 else {
702 $module = $mod;
703 last;
704 }
705 }
707 if( (!$msg) || (!$msg_hash) || (!$module)) {
708 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
709 }
711 return ($msg, $msg_hash, $module);
712 }
715 sub create_ciphering {
716 my ($passwd) = @_;
717 if((!defined($passwd)) || length($passwd)==0) {
718 $passwd = "";
719 }
720 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
721 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
722 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
723 $my_cipher->set_iv($iv);
724 return $my_cipher;
725 }
728 sub encrypt_msg {
729 my ($msg, $key) = @_;
730 my $my_cipher = &create_ciphering($key);
731 my $len;
732 {
733 use bytes;
734 $len= 16-length($msg)%16;
735 }
736 $msg = "\0"x($len).$msg;
737 $msg = $my_cipher->encrypt($msg);
738 chomp($msg = &encode_base64($msg));
739 # there are no newlines allowed inside msg
740 $msg=~ s/\n//g;
741 return $msg;
742 }
745 sub decrypt_msg {
747 my ($msg, $key) = @_ ;
748 $msg = &decode_base64($msg);
749 my $my_cipher = &create_ciphering($key);
750 $msg = $my_cipher->decrypt($msg);
751 $msg =~ s/\0*//g;
752 return $msg;
753 }
756 sub get_encrypt_key {
757 my ($target) = @_ ;
758 my $encrypt_key;
759 my $error = 0;
761 # target can be in known_server
762 if( not defined $encrypt_key ) {
763 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
764 my $query_res = $known_server_db->select_dbentry( $sql_statement );
765 while( my ($hit_num, $hit) = each %{ $query_res } ) {
766 my $host_name = $hit->{hostname};
767 if( $host_name ne $target ) {
768 next;
769 }
770 $encrypt_key = $hit->{hostkey};
771 last;
772 }
773 }
775 # target can be in known_client
776 if( not defined $encrypt_key ) {
777 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
778 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
779 while( my ($hit_num, $hit) = each %{ $query_res } ) {
780 my $host_name = $hit->{hostname};
781 if( $host_name ne $target ) {
782 next;
783 }
784 $encrypt_key = $hit->{hostkey};
785 last;
786 }
787 }
789 return $encrypt_key;
790 }
793 #=== FUNCTION ================================================================
794 # NAME: open_socket
795 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
796 # [PeerPort] string necessary if port not appended by PeerAddr
797 # RETURNS: socket IO::Socket::INET
798 # DESCRIPTION: open a socket to PeerAddr
799 #===============================================================================
800 sub open_socket {
801 my ($PeerAddr, $PeerPort) = @_ ;
802 if(defined($PeerPort)){
803 $PeerAddr = $PeerAddr.":".$PeerPort;
804 }
805 my $socket;
806 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
807 Porto => "tcp",
808 Type => SOCK_STREAM,
809 Timeout => 5,
810 );
811 if(not defined $socket) {
812 return;
813 }
814 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
815 return $socket;
816 }
819 #=== FUNCTION ================================================================
820 # NAME: get_ip
821 # PARAMETERS: interface name (i.e. eth0)
822 # RETURNS: (ip address)
823 # DESCRIPTION: Uses ioctl to get ip address directly from system.
824 #===============================================================================
825 sub get_ip {
826 my $ifreq= shift;
827 my $result= "";
828 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
829 my $proto= getprotobyname('ip');
831 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
832 or die "socket: $!";
834 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
835 my ($if, $sin) = unpack 'a16 a16', $ifreq;
836 my ($port, $addr) = sockaddr_in $sin;
837 my $ip = inet_ntoa $addr;
839 if ($ip && length($ip) > 0) {
840 $result = $ip;
841 }
842 }
844 return $result;
845 }
848 sub get_local_ip_for_remote_ip {
849 my $remote_ip= shift;
850 my $result="0.0.0.0";
852 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
853 if($remote_ip eq "127.0.0.1") {
854 $result = "127.0.0.1";
855 } else {
856 my $PROC_NET_ROUTE= ('/proc/net/route');
858 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
859 or die "Could not open $PROC_NET_ROUTE";
861 my @ifs = <PROC_NET_ROUTE>;
863 close(PROC_NET_ROUTE);
865 # Eat header line
866 shift @ifs;
867 chomp @ifs;
868 foreach my $line(@ifs) {
869 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
870 my $destination;
871 my $mask;
872 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
873 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
875 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
877 # destination matches route, save mac and exit
878 $result= &get_ip($Iface);
879 last;
880 }
881 }
882 }
883 } else {
884 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
885 }
886 return $result;
887 }
890 sub send_msg_to_target {
891 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
892 my $error = 0;
893 my $header;
894 my $new_status;
895 my $act_status;
896 my ($sql_statement, $res);
898 if( $msg_header ) {
899 $header = "'$msg_header'-";
900 } else {
901 $header = "";
902 }
904 # Patch the source ip
905 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
906 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
907 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
908 }
910 # encrypt xml msg
911 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
913 # opensocket
914 my $socket = &open_socket($address);
915 if( !$socket ) {
916 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
917 $error++;
918 }
920 if( $error == 0 ) {
921 # send xml msg
922 print $socket $crypted_msg."\n";
924 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
925 daemon_log("DEBUG: message:\n$msg", 9);
927 }
929 # close socket in any case
930 if( $socket ) {
931 close $socket;
932 }
934 if( $error > 0 ) { $new_status = "down"; }
935 else { $new_status = $msg_header; }
938 # known_clients
939 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
940 $res = $known_clients_db->select_dbentry($sql_statement);
941 if( keys(%$res) > 0) {
942 $act_status = $res->{1}->{'status'};
943 if ($act_status eq "down" && $new_status eq "down") {
944 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
945 $res = $known_clients_db->del_dbentry($sql_statement);
946 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
947 } else {
948 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
949 $res = $known_clients_db->update_dbentry($sql_statement);
950 if($new_status eq "down"){
951 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
952 } else {
953 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
954 }
955 }
956 }
958 # known_server
959 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
960 $res = $known_server_db->select_dbentry($sql_statement);
961 if( keys(%$res) > 0 ) {
962 $act_status = $res->{1}->{'status'};
963 if ($act_status eq "down" && $new_status eq "down") {
964 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
965 $res = $known_server_db->del_dbentry($sql_statement);
966 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
967 }
968 else {
969 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
970 $res = $known_server_db->update_dbentry($sql_statement);
971 if($new_status eq "down"){
972 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
973 }
974 else {
975 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
976 }
977 }
978 }
979 return $error;
980 }
983 sub update_jobdb_status_for_send_msgs {
984 my ($answer, $error) = @_;
985 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
986 my $jobdb_id = $1;
988 # sending msg faild
989 if( $error ) {
990 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
991 my $sql_statement = "UPDATE $job_queue_tn ".
992 "SET status='error', result='can not deliver msg, please consult log file' ".
993 "WHERE id=$jobdb_id";
994 my $res = $job_db->update_dbentry($sql_statement);
995 }
997 # sending msg was successful
998 } else {
999 my $sql_statement = "UPDATE $job_queue_tn ".
1000 "SET status='done' ".
1001 "WHERE id=$jobdb_id AND status='processed'";
1002 my $res = $job_db->update_dbentry($sql_statement);
1003 }
1004 }
1005 }
1007 sub _start {
1008 my ($kernel) = $_[KERNEL];
1009 &trigger_db_loop($kernel);
1010 $global_kernel = $kernel;
1011 $kernel->yield('register_at_foreign_servers');
1012 $kernel->yield('create_fai_server_db', $fai_server_tn );
1013 $kernel->yield('create_fai_release_db', $fai_release_tn );
1014 $kernel->sig(USR1 => "sig_handler");
1015 $kernel->sig(USR2 => "create_packages_list_db");
1016 }
1018 sub sig_handler {
1019 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1020 daemon_log("0 INFO got signal '$signal'", 1);
1021 $kernel->sig_handled();
1022 return;
1023 }
1026 sub msg_to_decrypt {
1027 my ($session, $heap) = @_[SESSION, HEAP];
1028 my $session_id = $session->ID;
1029 my ($msg, $msg_hash, $module);
1030 my $error = 0;
1032 # hole neue msg aus @msgs_to_decrypt
1033 my $next_msg = shift @msgs_to_decrypt;
1035 # entschlüssle sie
1037 # msg is from a new client or gosa
1038 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1039 # msg is from a gosa-si-server or gosa-si-bus
1040 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1042 }
1043 # msg is from a gosa-si-client
1044 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1045 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1046 }
1047 # an error occurred
1048 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1050 # could not understand a msg from its server the client cause a re-registering process
1051 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);
1052 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1053 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1054 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1055 my $host_name = $hit->{'hostname'};
1056 my $host_key = $hit->{'hostkey'};
1057 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1058 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1059 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1060 }
1061 $error++;
1062 }
1064 # add message to incoming_db
1065 if( $error == 0) {
1066 my $header = @{$msg_hash->{'header'}}[0];
1067 my $target = @{$msg_hash->{'target'}}[0];
1068 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1069 primkey=>[],
1070 headertag=>$header,
1071 targettag=>$target,
1072 xmlmessage=>$msg,
1073 timestamp=>&get_time,
1074 module=>$module,
1075 } );
1076 if ($res != 0) {
1077 # TODO ist das mit $! so ok???
1078 #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1);
1079 }
1080 }
1082 }
1085 sub next_task {
1086 my ($session, $heap) = @_[SESSION, HEAP];
1087 my $task = POE::Wheel::Run->new(
1088 Program => sub { process_task($session, $heap) },
1089 StdioFilter => POE::Filter::Reference->new(),
1090 StdoutEvent => "task_result",
1091 StderrEvent => "task_debug",
1092 CloseEvent => "task_done",
1093 );
1095 $heap->{task}->{ $task->ID } = $task;
1096 }
1098 sub handle_task_result {
1099 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1100 my $client_answer = $result->{'answer'};
1101 if( $client_answer =~ s/session_id=(\d+)$// ) {
1102 my $session_id = $1;
1103 if( defined $session_id ) {
1104 my $session_reference = $kernel->ID_id_to_session($session_id);
1105 if( defined $session_reference ) {
1106 $heap = $session_reference->get_heap();
1107 }
1108 }
1110 if(exists $heap->{'client'}) {
1111 $heap->{'client'}->put($client_answer);
1112 }
1113 }
1114 $kernel->sig(CHLD => "child_reap");
1115 }
1117 sub handle_task_debug {
1118 my $result = $_[ARG0];
1119 print STDERR "$result\n";
1120 }
1122 sub handle_task_done {
1123 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1124 delete $heap->{task}->{$task_id};
1125 }
1127 sub process_task {
1128 no strict "refs";
1129 my ($session, $heap, $input) = @_;
1130 my $session_id = $session->ID;
1131 my $error = 0;
1132 my $answer_l;
1133 my ($answer_header, @answer_target_l, $answer_source);
1134 my $client_answer = "";
1136 ##################################################
1137 # fetch first unprocessed message from incoming_db
1138 # sometimes the program is faster than sqlite, so wait until informations are present at db
1139 my $id_sql;
1140 my $id_res;
1141 my $message_id;
1142 # TODO : das hier ist sehr sehr unschön
1143 # to be tested: speed enhancement with usleep 100000???
1144 while (1) {
1145 $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')";
1146 $id_res = $incoming_db->exec_statement($id_sql);
1147 $message_id = @{@$id_res[0]}[0];
1148 if (defined $message_id) { last }
1149 usleep(100000);
1150 }
1152 # fetch new message from incoming_db
1153 my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id";
1154 my $res = $incoming_db->exec_statement($sql);
1156 # prepare all variables needed to process message
1157 my $msg = @{@$res[0]}[4];
1158 my $incoming_id = @{@$res[0]}[0];
1159 my $module = @{@$res[0]}[5];
1160 my $header = @{@$res[0]}[2];
1161 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1163 # messages which are an answer to a still running process should not be processed here
1164 if ($header =~ /^answer_(\d+)/) {
1165 return;
1166 }
1168 # delete message from db
1169 my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1170 my $delete_res = $incoming_db->exec_statement($delete_sql);
1172 ######################
1173 # process incoming msg
1174 if( $error == 0) {
1175 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1176 "' from '".$heap->{'remote_ip'}."'", 5);
1177 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1178 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1180 if ( 0 < @{$answer_l} ) {
1181 my $answer_str = join("\n", @{$answer_l});
1182 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1183 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1184 }
1185 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1186 } else {
1187 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1188 }
1190 }
1191 if( !$answer_l ) { $error++ };
1193 ########
1194 # answer
1195 if( $error == 0 ) {
1197 foreach my $answer ( @{$answer_l} ) {
1198 # check outgoing msg to xml validity
1199 my $answer_hash = &check_outgoing_xml_validity($answer);
1200 if( not defined $answer_hash ) { next; }
1202 $answer_header = @{$answer_hash->{'header'}}[0];
1203 @answer_target_l = @{$answer_hash->{'target'}};
1204 $answer_source = @{$answer_hash->{'source'}}[0];
1206 # deliver msg to all targets
1207 foreach my $answer_target ( @answer_target_l ) {
1209 # targets of msg are all gosa-si-clients in known_clients_db
1210 if( $answer_target eq "*" ) {
1211 # answer is for all clients
1212 my $sql_statement= "SELECT * FROM known_clients";
1213 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1214 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1215 my $host_name = $hit->{hostname};
1216 my $host_key = $hit->{hostkey};
1217 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1218 &update_jobdb_status_for_send_msgs($answer, $error);
1219 }
1220 }
1222 # targets of msg are all gosa-si-server in known_server_db
1223 elsif( $answer_target eq "KNOWN_SERVER" ) {
1224 # answer is for all server in known_server
1225 my $sql_statement= "SELECT * FROM known_server";
1226 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1227 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1228 my $host_name = $hit->{hostname};
1229 my $host_key = $hit->{hostkey};
1230 $answer =~ s/KNOWN_SERVER/$host_name/g;
1231 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1232 &update_jobdb_status_for_send_msgs($answer, $error);
1233 }
1234 }
1236 # target of msg is GOsa
1237 elsif( $answer_target eq "GOSA" ) {
1238 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1239 my $add_on = "";
1240 if( defined $session_id ) {
1241 $add_on = ".session_id=$session_id";
1242 }
1243 # answer is for GOSA and has to returned to connected client
1244 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1245 $client_answer = $gosa_answer.$add_on;
1246 }
1248 # target of msg is job queue at this host
1249 elsif( $answer_target eq "JOBDB") {
1250 $answer =~ /<header>(\S+)<\/header>/;
1251 my $header;
1252 if( defined $1 ) { $header = $1; }
1253 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1254 &update_jobdb_status_for_send_msgs($answer, $error);
1255 }
1257 # target of msg is a mac address
1258 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 ) {
1259 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1260 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1261 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1262 my $found_ip_flag = 0;
1263 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1264 my $host_name = $hit->{hostname};
1265 my $host_key = $hit->{hostkey};
1266 $answer =~ s/$answer_target/$host_name/g;
1267 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1268 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1269 &update_jobdb_status_for_send_msgs($answer, $error);
1270 $found_ip_flag++ ;
1271 }
1272 if( $found_ip_flag == 0) {
1273 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1274 if( $bus_activ eq "true" ) {
1275 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1276 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1277 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1278 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1279 my $bus_address = $hit->{hostname};
1280 my $bus_key = $hit->{hostkey};
1281 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1282 &update_jobdb_status_for_send_msgs($answer, $error);
1283 last;
1284 }
1285 }
1287 }
1289 # answer is for one specific host
1290 } else {
1291 # get encrypt_key
1292 my $encrypt_key = &get_encrypt_key($answer_target);
1293 if( not defined $encrypt_key ) {
1294 # unknown target, forward msg to bus
1295 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1296 if( $bus_activ eq "true" ) {
1297 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1298 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1299 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1300 my $res_length = keys( %{$query_res} );
1301 if( $res_length == 0 ){
1302 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1303 "no bus found in known_server", 3);
1304 }
1305 else {
1306 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1307 my $bus_key = $hit->{hostkey};
1308 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1309 &update_jobdb_status_for_send_msgs($answer, $error);
1310 }
1311 }
1312 }
1313 next;
1314 }
1315 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1316 &update_jobdb_status_for_send_msgs($answer, $error);
1317 }
1318 }
1319 }
1320 }
1322 my $filter = POE::Filter::Reference->new();
1323 my %result = (
1324 status => "seems ok to me",
1325 answer => $client_answer,
1326 );
1328 my $output = $filter->put( [ \%result ] );
1329 print @$output;
1332 }
1335 sub trigger_db_loop {
1336 my ($kernel) = @_ ;
1337 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1338 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1339 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1340 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1341 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1342 }
1345 sub watch_for_done_jobs {
1346 my ($kernel,$heap) = @_[KERNEL, HEAP];
1348 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1349 " WHERE status='done'";
1350 my $res = $job_db->select_dbentry( $sql_statement );
1352 while( my ($id, $hit) = each %{$res} ) {
1353 my $jobdb_id = $hit->{id};
1354 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1355 my $res = $job_db->del_dbentry($sql_statement);
1356 }
1358 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1359 }
1362 sub watch_for_new_jobs {
1363 if($watch_for_new_jobs_in_progress == 0) {
1364 $watch_for_new_jobs_in_progress = 1;
1365 my ($kernel,$heap) = @_[KERNEL, HEAP];
1367 # check gosa job queue for jobs with executable timestamp
1368 my $timestamp = &get_time();
1369 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1370 my $res = $job_db->exec_statement( $sql_statement );
1372 # Merge all new jobs that would do the same actions
1373 my @drops;
1374 my $hits;
1375 foreach my $hit (reverse @{$res} ) {
1376 my $macaddress= lc @{$hit}[8];
1377 my $headertag= @{$hit}[5];
1378 if(
1379 defined($hits->{$macaddress}) &&
1380 defined($hits->{$macaddress}->{$headertag}) &&
1381 defined($hits->{$macaddress}->{$headertag}[0])
1382 ) {
1383 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1384 }
1385 $hits->{$macaddress}->{$headertag}= $hit;
1386 }
1388 # Delete new jobs with a matching job in state 'processing'
1389 foreach my $macaddress (keys %{$hits}) {
1390 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1391 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1392 if(defined($jobdb_id)) {
1393 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1394 my $res = $job_db->exec_statement( $sql_statement );
1395 foreach my $hit (@{$res}) {
1396 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1397 }
1398 } else {
1399 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1400 }
1401 }
1402 }
1404 # Commit deletion
1405 $job_db->exec_statementlist(\@drops);
1407 # Look for new jobs that could be executed
1408 foreach my $macaddress (keys %{$hits}) {
1410 # Look if there is an executing job
1411 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1412 my $res = $job_db->exec_statement( $sql_statement );
1414 # Skip new jobs for host if there is a processing job
1415 if(defined($res) and defined @{$res}[0]) {
1416 next;
1417 }
1419 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1420 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1421 if(defined($jobdb_id)) {
1422 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1424 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1425 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1426 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1428 # expect macaddress is unique!!!!!!
1429 my $target = $res_hash->{1}->{hostname};
1431 # change header
1432 $job_msg =~ s/<header>job_/<header>gosa_/;
1434 # add sqlite_id
1435 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1437 $job_msg =~ /<header>(\S+)<\/header>/;
1438 my $header = $1 ;
1439 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1441 # update status in job queue to 'processing'
1442 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1443 my $res = $job_db->update_dbentry($sql_statement);
1444 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1446 # We don't want parallel processing
1447 last;
1448 }
1449 }
1450 }
1452 $watch_for_new_jobs_in_progress = 0;
1453 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1454 }
1455 }
1458 sub watch_for_new_messages {
1459 my ($kernel,$heap) = @_[KERNEL, HEAP];
1460 my @coll_user_msg; # collection list of outgoing messages
1462 # check messaging_db for new incoming messages with executable timestamp
1463 my $timestamp = &get_time();
1464 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1465 my $res = $messaging_db->exec_statement( $sql_statement );
1466 foreach my $hit (@{$res}) {
1468 # create outgoing messages
1469 my $message_to = @{$hit}[3];
1470 # translate message_to to plain login name
1471 my @message_to_l = split(/,/, $message_to);
1472 my %receiver_h;
1473 foreach my $receiver (@message_to_l) {
1474 if ($receiver =~ /^u_([\s\S]*)$/) {
1475 $receiver_h{$1} = 0;
1476 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1477 my $group_name = $1;
1478 # fetch all group members from ldap and add them to receiver hash
1479 my $ldap_handle = &get_ldap_handle();
1480 if (defined $ldap_handle) {
1481 my $mesg = $ldap_handle->search(
1482 base => $ldap_base,
1483 scope => 'sub',
1484 attrs => ['memberUid'],
1485 filter => "cn=$group_name",
1486 );
1487 if ($mesg->count) {
1488 my @entries = $mesg->entries;
1489 foreach my $entry (@entries) {
1490 my @receivers= $entry->get_value("memberUid");
1491 foreach my $receiver (@receivers) {
1492 $receiver_h{$1} = 0;
1493 }
1494 }
1495 }
1496 # translating errors ?
1497 if ($mesg->code) {
1498 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1499 }
1500 # ldap handle error ?
1501 } else {
1502 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1503 }
1504 } else {
1505 my $sbjct = &encode_base64(@{$hit}[1]);
1506 my $msg = &encode_base64(@{$hit}[7]);
1507 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1508 }
1509 }
1510 my @receiver_l = keys(%receiver_h);
1512 my $message_id = @{$hit}[0];
1514 #add each outgoing msg to messaging_db
1515 my $receiver;
1516 foreach $receiver (@receiver_l) {
1517 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1518 "VALUES ('".
1519 $message_id."', '". # id
1520 @{$hit}[1]."', '". # subject
1521 @{$hit}[2]."', '". # message_from
1522 $receiver."', '". # message_to
1523 "none"."', '". # flag
1524 "out"."', '". # direction
1525 @{$hit}[6]."', '". # delivery_time
1526 @{$hit}[7]."', '". # message
1527 $timestamp."'". # timestamp
1528 ")";
1529 &daemon_log("M DEBUG: $sql_statement", 1);
1530 my $res = $messaging_db->exec_statement($sql_statement);
1531 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1532 }
1534 # set incoming message to flag d=deliverd
1535 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1536 &daemon_log("M DEBUG: $sql_statement", 7);
1537 $res = $messaging_db->update_dbentry($sql_statement);
1538 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1539 }
1541 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1542 return;
1543 }
1545 sub watch_for_delivery_messages {
1546 my ($kernel, $heap) = @_[KERNEL, HEAP];
1548 # select outgoing messages
1549 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1550 #&daemon_log("0 DEBUG: $sql", 7);
1551 my $res = $messaging_db->exec_statement( $sql_statement );
1553 # build out msg for each usr
1554 foreach my $hit (@{$res}) {
1555 my $receiver = @{$hit}[3];
1556 my $msg_id = @{$hit}[0];
1557 my $subject = @{$hit}[1];
1558 my $message = @{$hit}[7];
1560 # resolve usr -> host where usr is logged in
1561 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1562 #&daemon_log("0 DEBUG: $sql", 7);
1563 my $res = $login_users_db->exec_statement($sql);
1565 # reciver is logged in nowhere
1566 if (not ref(@$res[0]) eq "ARRAY") { next; }
1568 my $send_succeed = 0;
1569 foreach my $hit (@$res) {
1570 my $receiver_host = @$hit[0];
1571 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1573 # fetch key to encrypt msg propperly for usr/host
1574 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1575 &daemon_log("0 DEBUG: $sql", 7);
1576 my $res = $known_clients_db->exec_statement($sql);
1578 # host is already down
1579 if (not ref(@$res[0]) eq "ARRAY") { next; }
1581 # host is on
1582 my $receiver_key = @{@{$res}[0]}[2];
1583 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1584 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1585 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1586 if ($error == 0 ) {
1587 $send_succeed++ ;
1588 }
1589 }
1591 if ($send_succeed) {
1592 # set outgoing msg at db to deliverd
1593 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1594 &daemon_log("0 DEBUG: $sql", 7);
1595 my $res = $messaging_db->exec_statement($sql);
1596 }
1597 }
1599 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1600 return;
1601 }
1604 sub watch_for_done_messages {
1605 my ($kernel,$heap) = @_[KERNEL, HEAP];
1607 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1608 #&daemon_log("0 DEBUG: $sql", 7);
1609 my $res = $messaging_db->exec_statement($sql);
1611 foreach my $hit (@{$res}) {
1612 my $msg_id = @{$hit}[0];
1614 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1615 #&daemon_log("0 DEBUG: $sql", 7);
1616 my $res = $messaging_db->exec_statement($sql);
1618 # not all usr msgs have been seen till now
1619 if ( ref(@$res[0]) eq "ARRAY") { next; }
1621 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1622 #&daemon_log("0 DEBUG: $sql", 7);
1623 $res = $messaging_db->exec_statement($sql);
1625 }
1627 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1628 return;
1629 }
1632 sub get_ldap_handle {
1633 my ($session_id) = @_;
1634 my $heap;
1635 my $ldap_handle;
1637 if (not defined $session_id ) { $session_id = 0 };
1638 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1640 if ($session_id == 0) {
1641 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1642 $ldap_handle = Net::LDAP->new( $ldap_uri );
1643 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1645 } else {
1646 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1647 if( defined $session_reference ) {
1648 $heap = $session_reference->get_heap();
1649 }
1651 if (not defined $heap) {
1652 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1653 return;
1654 }
1656 # TODO: This "if" is nonsense, because it doesn't prove that the
1657 # used handle is still valid - or if we've to reconnect...
1658 #if (not exists $heap->{ldap_handle}) {
1659 $ldap_handle = Net::LDAP->new( $ldap_uri );
1660 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1661 $heap->{ldap_handle} = $ldap_handle;
1662 #}
1663 }
1664 return $ldap_handle;
1665 }
1668 sub change_fai_state {
1669 my ($st, $targets, $session_id) = @_;
1670 $session_id = 0 if not defined $session_id;
1671 # Set FAI state to localboot
1672 my %mapActions= (
1673 reboot => '',
1674 update => 'softupdate',
1675 localboot => 'localboot',
1676 reinstall => 'install',
1677 rescan => '',
1678 wake => '',
1679 memcheck => 'memcheck',
1680 sysinfo => 'sysinfo',
1681 install => 'install',
1682 );
1684 # Return if this is unknown
1685 if (!exists $mapActions{ $st }){
1686 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1687 return;
1688 }
1690 my $state= $mapActions{ $st };
1692 my $ldap_handle = &get_ldap_handle($session_id);
1693 if( defined($ldap_handle) ) {
1695 # Build search filter for hosts
1696 my $search= "(&(objectClass=GOhard)";
1697 foreach (@{$targets}){
1698 $search.= "(macAddress=$_)";
1699 }
1700 $search.= ")";
1702 # If there's any host inside of the search string, procress them
1703 if (!($search =~ /macAddress/)){
1704 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1705 return;
1706 }
1708 # Perform search for Unit Tag
1709 my $mesg = $ldap_handle->search(
1710 base => $ldap_base,
1711 scope => 'sub',
1712 attrs => ['dn', 'FAIstate', 'objectClass'],
1713 filter => "$search"
1714 );
1716 if ($mesg->count) {
1717 my @entries = $mesg->entries;
1718 foreach my $entry (@entries) {
1719 # Only modify entry if it is not set to '$state'
1720 if ($entry->get_value("FAIstate") ne "$state"){
1721 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1722 my $result;
1723 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1724 if (exists $tmp{'FAIobject'}){
1725 if ($state eq ''){
1726 $result= $ldap_handle->modify($entry->dn, changes => [
1727 delete => [ FAIstate => [] ] ]);
1728 } else {
1729 $result= $ldap_handle->modify($entry->dn, changes => [
1730 replace => [ FAIstate => $state ] ]);
1731 }
1732 } elsif ($state ne ''){
1733 $result= $ldap_handle->modify($entry->dn, changes => [
1734 add => [ objectClass => 'FAIobject' ],
1735 add => [ FAIstate => $state ] ]);
1736 }
1738 # Errors?
1739 if ($result->code){
1740 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1741 }
1742 } else {
1743 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1744 }
1745 }
1746 }
1747 # if no ldap handle defined
1748 } else {
1749 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1750 }
1752 }
1755 sub change_goto_state {
1756 my ($st, $targets, $session_id) = @_;
1757 $session_id = 0 if not defined $session_id;
1759 # Switch on or off?
1760 my $state= $st eq 'active' ? 'active': 'locked';
1762 my $ldap_handle = &get_ldap_handle($session_id);
1763 if( defined($ldap_handle) ) {
1765 # Build search filter for hosts
1766 my $search= "(&(objectClass=GOhard)";
1767 foreach (@{$targets}){
1768 $search.= "(macAddress=$_)";
1769 }
1770 $search.= ")";
1772 # If there's any host inside of the search string, procress them
1773 if (!($search =~ /macAddress/)){
1774 return;
1775 }
1777 # Perform search for Unit Tag
1778 my $mesg = $ldap_handle->search(
1779 base => $ldap_base,
1780 scope => 'sub',
1781 attrs => ['dn', 'gotoMode'],
1782 filter => "$search"
1783 );
1785 if ($mesg->count) {
1786 my @entries = $mesg->entries;
1787 foreach my $entry (@entries) {
1789 # Only modify entry if it is not set to '$state'
1790 if ($entry->get_value("gotoMode") ne $state){
1792 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1793 my $result;
1794 $result= $ldap_handle->modify($entry->dn, changes => [
1795 replace => [ gotoMode => $state ] ]);
1797 # Errors?
1798 if ($result->code){
1799 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1800 }
1802 }
1803 }
1804 }
1806 }
1807 }
1810 sub run_create_fai_server_db {
1811 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1812 my $session_id = $session->ID;
1813 my $task = POE::Wheel::Run->new(
1814 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1815 StdoutEvent => "session_run_result",
1816 StderrEvent => "session_run_debug",
1817 CloseEvent => "session_run_done",
1818 );
1820 $heap->{task}->{ $task->ID } = $task;
1821 return;
1822 }
1825 sub create_fai_server_db {
1826 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1827 my $result;
1829 if (not defined $session_id) { $session_id = 0; }
1830 my $ldap_handle = &get_ldap_handle();
1831 if(defined($ldap_handle)) {
1832 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1833 my $mesg= $ldap_handle->search(
1834 base => $ldap_base,
1835 scope => 'sub',
1836 attrs => ['FAIrepository', 'gosaUnitTag'],
1837 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1838 );
1839 if($mesg->{'resultCode'} == 0 &&
1840 $mesg->count != 0) {
1841 foreach my $entry (@{$mesg->{entries}}) {
1842 if($entry->exists('FAIrepository')) {
1843 # Add an entry for each Repository configured for server
1844 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1845 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1846 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1847 $result= $fai_server_db->add_dbentry( {
1848 table => $table_name,
1849 primkey => ['server', 'release', 'tag'],
1850 server => $tmp_url,
1851 release => $tmp_release,
1852 sections => $tmp_sections,
1853 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1854 } );
1855 }
1856 }
1857 }
1858 }
1859 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1861 # TODO: Find a way to post the 'create_packages_list_db' event
1862 if(not defined($dont_create_packages_list)) {
1863 &create_packages_list_db(undef, undef, $session_id);
1864 }
1865 }
1867 $ldap_handle->disconnect;
1868 return $result;
1869 }
1872 sub run_create_fai_release_db {
1873 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1874 my $session_id = $session->ID;
1875 my $task = POE::Wheel::Run->new(
1876 Program => sub { &create_fai_release_db($table_name, $session_id) },
1877 StdoutEvent => "session_run_result",
1878 StderrEvent => "session_run_debug",
1879 CloseEvent => "session_run_done",
1880 );
1882 $heap->{task}->{ $task->ID } = $task;
1883 return;
1884 }
1887 sub create_fai_release_db {
1888 my ($table_name, $session_id) = @_;
1889 my $result;
1891 # used for logging
1892 if (not defined $session_id) { $session_id = 0; }
1894 my $ldap_handle = &get_ldap_handle();
1895 if(defined($ldap_handle)) {
1896 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1897 my $mesg= $ldap_handle->search(
1898 base => $ldap_base,
1899 scope => 'sub',
1900 attrs => [],
1901 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1902 );
1903 if($mesg->{'resultCode'} == 0 &&
1904 $mesg->count != 0) {
1905 # Walk through all possible FAI container ou's
1906 my @sql_list;
1907 my $timestamp= &get_time();
1908 foreach my $ou (@{$mesg->{entries}}) {
1909 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1910 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1911 my @tmp_array=get_fai_release_entries($tmp_classes);
1912 if(@tmp_array) {
1913 foreach my $entry (@tmp_array) {
1914 if(defined($entry) && ref($entry) eq 'HASH') {
1915 my $sql=
1916 "INSERT INTO $table_name "
1917 ."(timestamp, release, class, type, state) VALUES ("
1918 .$timestamp.","
1919 ."'".$entry->{'release'}."',"
1920 ."'".$entry->{'class'}."',"
1921 ."'".$entry->{'type'}."',"
1922 ."'".$entry->{'state'}."')";
1923 push @sql_list, $sql;
1924 }
1925 }
1926 }
1927 }
1928 }
1930 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1931 if(@sql_list) {
1932 unshift @sql_list, "VACUUM";
1933 unshift @sql_list, "DELETE FROM $table_name";
1934 $fai_release_db->exec_statementlist(\@sql_list);
1935 }
1936 daemon_log("$session_id DEBUG: Done with inserting",7);
1937 }
1938 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1939 }
1940 $ldap_handle->disconnect;
1941 return $result;
1942 }
1944 sub get_fai_types {
1945 my $tmp_classes = shift || return undef;
1946 my @result;
1948 foreach my $type(keys %{$tmp_classes}) {
1949 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1950 my $entry = {
1951 type => $type,
1952 state => $tmp_classes->{$type}[0],
1953 };
1954 push @result, $entry;
1955 }
1956 }
1958 return @result;
1959 }
1961 sub get_fai_state {
1962 my $result = "";
1963 my $tmp_classes = shift || return $result;
1965 foreach my $type(keys %{$tmp_classes}) {
1966 if(defined($tmp_classes->{$type}[0])) {
1967 $result = $tmp_classes->{$type}[0];
1969 # State is equal for all types in class
1970 last;
1971 }
1972 }
1974 return $result;
1975 }
1977 sub resolve_fai_classes {
1978 my ($fai_base, $ldap_handle, $session_id) = @_;
1979 if (not defined $session_id) { $session_id = 0; }
1980 my $result;
1981 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1982 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1983 my $fai_classes;
1985 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1986 my $mesg= $ldap_handle->search(
1987 base => $fai_base,
1988 scope => 'sub',
1989 attrs => ['cn','objectClass','FAIstate'],
1990 filter => $fai_filter,
1991 );
1992 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1994 if($mesg->{'resultCode'} == 0 &&
1995 $mesg->count != 0) {
1996 foreach my $entry (@{$mesg->{entries}}) {
1997 if($entry->exists('cn')) {
1998 my $tmp_dn= $entry->dn();
2000 # Skip classname and ou dn parts for class
2001 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2003 # Skip classes without releases
2004 if((!defined($tmp_release)) || length($tmp_release)==0) {
2005 next;
2006 }
2008 my $tmp_cn= $entry->get_value('cn');
2009 my $tmp_state= $entry->get_value('FAIstate');
2011 my $tmp_type;
2012 # Get FAI type
2013 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2014 if(grep $_ eq $oclass, @possible_fai_classes) {
2015 $tmp_type= $oclass;
2016 last;
2017 }
2018 }
2020 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2021 # A Subrelease
2022 my @sub_releases = split(/,/, $tmp_release);
2024 # Walk through subreleases and build hash tree
2025 my $hash;
2026 while(my $tmp_sub_release = pop @sub_releases) {
2027 $hash .= "\{'$tmp_sub_release'\}->";
2028 }
2029 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2030 } else {
2031 # A branch, no subrelease
2032 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2033 }
2034 } elsif (!$entry->exists('cn')) {
2035 my $tmp_dn= $entry->dn();
2036 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2038 # Skip classes without releases
2039 if((!defined($tmp_release)) || length($tmp_release)==0) {
2040 next;
2041 }
2043 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2044 # A Subrelease
2045 my @sub_releases= split(/,/, $tmp_release);
2047 # Walk through subreleases and build hash tree
2048 my $hash;
2049 while(my $tmp_sub_release = pop @sub_releases) {
2050 $hash .= "\{'$tmp_sub_release'\}->";
2051 }
2052 # Remove the last two characters
2053 chop($hash);
2054 chop($hash);
2056 eval('$fai_classes->'.$hash.'= {}');
2057 } else {
2058 # A branch, no subrelease
2059 if(!exists($fai_classes->{$tmp_release})) {
2060 $fai_classes->{$tmp_release} = {};
2061 }
2062 }
2063 }
2064 }
2066 # The hash is complete, now we can honor the copy-on-write based missing entries
2067 foreach my $release (keys %$fai_classes) {
2068 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2069 }
2070 }
2071 return $result;
2072 }
2074 sub apply_fai_inheritance {
2075 my $fai_classes = shift || return {};
2076 my $tmp_classes;
2078 # Get the classes from the branch
2079 foreach my $class (keys %{$fai_classes}) {
2080 # Skip subreleases
2081 if($class =~ /^ou=.*$/) {
2082 next;
2083 } else {
2084 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2085 }
2086 }
2088 # Apply to each subrelease
2089 foreach my $subrelease (keys %{$fai_classes}) {
2090 if($subrelease =~ /ou=/) {
2091 foreach my $tmp_class (keys %{$tmp_classes}) {
2092 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2093 $fai_classes->{$subrelease}->{$tmp_class} =
2094 deep_copy($tmp_classes->{$tmp_class});
2095 } else {
2096 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2097 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2098 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2099 deep_copy($tmp_classes->{$tmp_class}->{$type});
2100 }
2101 }
2102 }
2103 }
2104 }
2105 }
2107 # Find subreleases in deeper levels
2108 foreach my $subrelease (keys %{$fai_classes}) {
2109 if($subrelease =~ /ou=/) {
2110 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2111 if($subsubrelease =~ /ou=/) {
2112 apply_fai_inheritance($fai_classes->{$subrelease});
2113 }
2114 }
2115 }
2116 }
2118 return $fai_classes;
2119 }
2121 sub get_fai_release_entries {
2122 my $tmp_classes = shift || return;
2123 my $parent = shift || "";
2124 my @result = shift || ();
2126 foreach my $entry (keys %{$tmp_classes}) {
2127 if(defined($entry)) {
2128 if($entry =~ /^ou=.*$/) {
2129 my $release_name = $entry;
2130 $release_name =~ s/ou=//g;
2131 if(length($parent)>0) {
2132 $release_name = $parent."/".$release_name;
2133 }
2134 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2135 foreach my $bufentry(@bufentries) {
2136 push @result, $bufentry;
2137 }
2138 } else {
2139 my @types = get_fai_types($tmp_classes->{$entry});
2140 foreach my $type (@types) {
2141 push @result,
2142 {
2143 'class' => $entry,
2144 'type' => $type->{'type'},
2145 'release' => $parent,
2146 'state' => $type->{'state'},
2147 };
2148 }
2149 }
2150 }
2151 }
2153 return @result;
2154 }
2156 sub deep_copy {
2157 my $this = shift;
2158 if (not ref $this) {
2159 $this;
2160 } elsif (ref $this eq "ARRAY") {
2161 [map deep_copy($_), @$this];
2162 } elsif (ref $this eq "HASH") {
2163 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2164 } else { die "what type is $_?" }
2165 }
2168 sub session_run_result {
2169 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2170 $kernel->sig(CHLD => "child_reap");
2171 }
2173 sub session_run_debug {
2174 my $result = $_[ARG0];
2175 print STDERR "$result\n";
2176 }
2178 sub session_run_done {
2179 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2180 delete $heap->{task}->{$task_id};
2181 }
2184 sub create_sources_list {
2185 my $session_id = shift;
2186 my $ldap_handle = &main::get_ldap_handle;
2187 my $result="/tmp/gosa_si_tmp_sources_list";
2189 # Remove old file
2190 if(stat($result)) {
2191 unlink($result);
2192 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2193 }
2195 my $fh;
2196 open($fh, ">$result");
2197 if (not defined $fh) {
2198 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2199 return undef;
2200 }
2201 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2202 my $mesg=$ldap_handle->search(
2203 base => $main::ldap_server_dn,
2204 scope => 'base',
2205 attrs => 'FAIrepository',
2206 filter => 'objectClass=FAIrepositoryServer'
2207 );
2208 if($mesg->count) {
2209 foreach my $entry(@{$mesg->{'entries'}}) {
2210 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2211 my ($server, $tag, $release, $sections)= split /\|/, $value;
2212 my $line = "deb $server $release";
2213 $sections =~ s/,/ /g;
2214 $line.= " $sections";
2215 print $fh $line."\n";
2216 }
2217 }
2218 }
2219 } else {
2220 if (defined $main::ldap_server_dn){
2221 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2222 } else {
2223 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2224 }
2225 }
2226 close($fh);
2228 return $result;
2229 }
2232 sub run_create_packages_list_db {
2233 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2234 my $session_id = $session->ID;
2236 my $task = POE::Wheel::Run->new(
2237 Priority => +20,
2238 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2239 StdoutEvent => "session_run_result",
2240 StderrEvent => "session_run_debug",
2241 CloseEvent => "session_run_done",
2242 );
2243 $heap->{task}->{ $task->ID } = $task;
2244 }
2247 sub create_packages_list_db {
2248 my ($ldap_handle, $sources_file, $session_id) = @_;
2250 # it should not be possible to trigger a recreation of packages_list_db
2251 # while packages_list_db is under construction, so set flag packages_list_under_construction
2252 # which is tested befor recreation can be started
2253 if (-r $packages_list_under_construction) {
2254 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2255 return;
2256 } else {
2257 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2258 # set packages_list_under_construction to true
2259 system("touch $packages_list_under_construction");
2260 @packages_list_statements=();
2261 }
2263 if (not defined $session_id) { $session_id = 0; }
2264 if (not defined $ldap_handle) {
2265 $ldap_handle= &get_ldap_handle();
2267 if (not defined $ldap_handle) {
2268 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2269 unlink($packages_list_under_construction);
2270 return;
2271 }
2272 }
2273 if (not defined $sources_file) {
2274 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2275 $sources_file = &create_sources_list($session_id);
2276 }
2278 if (not defined $sources_file) {
2279 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2280 unlink($packages_list_under_construction);
2281 return;
2282 }
2284 my $line;
2286 open(CONFIG, "<$sources_file") or do {
2287 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2288 unlink($packages_list_under_construction);
2289 return;
2290 };
2292 # Read lines
2293 while ($line = <CONFIG>){
2294 # Unify
2295 chop($line);
2296 $line =~ s/^\s+//;
2297 $line =~ s/^\s+/ /;
2299 # Strip comments
2300 $line =~ s/#.*$//g;
2302 # Skip empty lines
2303 if ($line =~ /^\s*$/){
2304 next;
2305 }
2307 # Interpret deb line
2308 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2309 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2310 my $section;
2311 foreach $section (split(' ', $sections)){
2312 &parse_package_info( $baseurl, $dist, $section, $session_id );
2313 }
2314 }
2315 }
2317 close (CONFIG);
2319 find(\&cleanup_and_extract, keys( %repo_dirs ));
2320 &main::strip_packages_list_statements();
2321 unshift @packages_list_statements, "VACUUM";
2322 $packages_list_db->exec_statementlist(\@packages_list_statements);
2323 unlink($packages_list_under_construction);
2324 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2325 return;
2326 }
2328 # This function should do some intensive task to minimize the db-traffic
2329 sub strip_packages_list_statements {
2330 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2331 my @new_statement_list=();
2332 my $hash;
2333 my $insert_hash;
2334 my $update_hash;
2335 my $delete_hash;
2336 my $local_timestamp=get_time();
2338 foreach my $existing_entry (@existing_entries) {
2339 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2340 }
2342 foreach my $statement (@packages_list_statements) {
2343 if($statement =~ /^INSERT/i) {
2344 # Assign the values from the insert statement
2345 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2346 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2347 if(exists($hash->{$distribution}->{$package}->{$version})) {
2348 # If section or description has changed, update the DB
2349 if(
2350 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2351 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2352 ) {
2353 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2354 }
2355 } else {
2356 # Insert a non-existing entry to db
2357 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2358 }
2359 } elsif ($statement =~ /^UPDATE/i) {
2360 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2361 /^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;
2362 foreach my $distribution (keys %{$hash}) {
2363 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2364 # update the insertion hash to execute only one query per package (insert instead insert+update)
2365 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2366 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2367 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2368 my $section;
2369 my $description;
2370 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2371 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2372 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2373 }
2374 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2375 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2376 }
2377 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2378 }
2379 }
2380 }
2381 }
2382 }
2384 # TODO: Check for orphaned entries
2386 # unroll the insert_hash
2387 foreach my $distribution (keys %{$insert_hash}) {
2388 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2389 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2390 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2391 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2392 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2393 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2394 ."'$local_timestamp')";
2395 }
2396 }
2397 }
2399 # unroll the update hash
2400 foreach my $distribution (keys %{$update_hash}) {
2401 foreach my $package (keys %{$update_hash->{$distribution}}) {
2402 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2403 my $set = "";
2404 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2405 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2406 }
2407 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2408 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2409 }
2410 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2411 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2412 }
2413 if(defined($set) and length($set) > 0) {
2414 $set .= "timestamp = '$local_timestamp'";
2415 } else {
2416 next;
2417 }
2418 push @new_statement_list,
2419 "UPDATE $main::packages_list_tn SET $set WHERE"
2420 ." distribution = '$distribution'"
2421 ." AND package = '$package'"
2422 ." AND version = '$version'";
2423 }
2424 }
2425 }
2427 @packages_list_statements = @new_statement_list;
2428 }
2431 sub parse_package_info {
2432 my ($baseurl, $dist, $section, $session_id)= @_;
2433 my ($package);
2434 if (not defined $session_id) { $session_id = 0; }
2435 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2436 $repo_dirs{ "${repo_path}/pool" } = 1;
2438 foreach $package ("Packages.gz"){
2439 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2440 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2441 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2442 }
2444 }
2447 sub get_package {
2448 my ($url, $dest, $session_id)= @_;
2449 if (not defined $session_id) { $session_id = 0; }
2451 my $tpath = dirname($dest);
2452 -d "$tpath" || mkpath "$tpath";
2454 # This is ugly, but I've no time to take a look at "how it works in perl"
2455 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2456 system("gunzip -cd '$dest' > '$dest.in'");
2457 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2458 unlink($dest);
2459 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2460 } else {
2461 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2462 }
2463 return 0;
2464 }
2467 sub parse_package {
2468 my ($path, $dist, $srv_path, $session_id)= @_;
2469 if (not defined $session_id) { $session_id = 0;}
2470 my ($package, $version, $section, $description);
2471 my $PACKAGES;
2472 my $timestamp = &get_time();
2474 if(not stat("$path.in")) {
2475 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2476 return;
2477 }
2479 open($PACKAGES, "<$path.in");
2480 if(not defined($PACKAGES)) {
2481 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2482 return;
2483 }
2485 # Read lines
2486 while (<$PACKAGES>){
2487 my $line = $_;
2488 # Unify
2489 chop($line);
2491 # Use empty lines as a trigger
2492 if ($line =~ /^\s*$/){
2493 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2494 push(@packages_list_statements, $sql);
2495 $package = "none";
2496 $version = "none";
2497 $section = "none";
2498 $description = "none";
2499 next;
2500 }
2502 # Trigger for package name
2503 if ($line =~ /^Package:\s/){
2504 ($package)= ($line =~ /^Package: (.*)$/);
2505 next;
2506 }
2508 # Trigger for version
2509 if ($line =~ /^Version:\s/){
2510 ($version)= ($line =~ /^Version: (.*)$/);
2511 next;
2512 }
2514 # Trigger for description
2515 if ($line =~ /^Description:\s/){
2516 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2517 next;
2518 }
2520 # Trigger for section
2521 if ($line =~ /^Section:\s/){
2522 ($section)= ($line =~ /^Section: (.*)$/);
2523 next;
2524 }
2526 # Trigger for filename
2527 if ($line =~ /^Filename:\s/){
2528 my ($filename) = ($line =~ /^Filename: (.*)$/);
2529 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2530 next;
2531 }
2532 }
2534 close( $PACKAGES );
2535 unlink( "$path.in" );
2536 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2537 }
2540 sub store_fileinfo {
2541 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2543 my %fileinfo = (
2544 'package' => $package,
2545 'dist' => $dist,
2546 'version' => $vers,
2547 );
2549 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2550 }
2553 sub cleanup_and_extract {
2554 my $fileinfo = $repo_files{ $File::Find::name };
2556 if( defined $fileinfo ) {
2558 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2559 my $sql;
2560 my $package = $fileinfo->{ 'package' };
2561 my $newver = $fileinfo->{ 'version' };
2563 mkpath($dir);
2564 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2566 if( -f "$dir/DEBIAN/templates" ) {
2568 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2570 my $tmpl= "";
2571 {
2572 local $/=undef;
2573 open FILE, "$dir/DEBIAN/templates";
2574 $tmpl = &encode_base64(<FILE>);
2575 close FILE;
2576 }
2577 rmtree("$dir/DEBIAN/templates");
2579 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2580 push @packages_list_statements, $sql;
2581 }
2582 }
2584 return;
2585 }
2588 sub register_at_foreign_servers {
2589 my ($kernel) = $_[KERNEL];
2591 # hole alle bekannten server aus known_server_db
2592 my $server_sql = "SELECT * FROM $known_server_tn";
2593 my $server_res = $known_server_db->exec_statement($server_sql);
2595 # no entries in known_server_db
2596 if (not ref(@$server_res[0]) eq "ARRAY") {
2597 # TODO
2598 }
2600 # detect already connected clients
2601 my $client_sql = "SELECT * FROM $known_clients_tn";
2602 my $client_res = $known_clients_db->exec_statement($client_sql);
2604 # send my server details to all other gosa-si-server within the network
2605 foreach my $hit (@$server_res) {
2606 my $hostname = @$hit[0];
2607 my $hostkey = &create_passwd;
2609 # add already connected clients to registration message
2610 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2611 &add_content2xml_hash($myhash, 'key', $hostkey);
2612 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2614 # build registration message and send it
2615 my $foreign_server_msg = &create_xml_string($myhash);
2616 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2617 }
2619 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2620 return;
2621 }
2624 #==== MAIN = main ==============================================================
2625 # parse commandline options
2626 Getopt::Long::Configure( "bundling" );
2627 GetOptions("h|help" => \&usage,
2628 "c|config=s" => \$cfg_file,
2629 "f|foreground" => \$foreground,
2630 "v|verbose+" => \$verbose,
2631 "no-bus+" => \$no_bus,
2632 "no-arp+" => \$no_arp,
2633 );
2635 # read and set config parameters
2636 &check_cmdline_param ;
2637 &read_configfile;
2638 &check_pid;
2640 $SIG{CHLD} = 'IGNORE';
2642 # forward error messages to logfile
2643 if( ! $foreground ) {
2644 open( STDIN, '+>/dev/null' );
2645 open( STDOUT, '+>&STDIN' );
2646 open( STDERR, '+>&STDIN' );
2647 }
2649 # Just fork, if we are not in foreground mode
2650 if( ! $foreground ) {
2651 chdir '/' or die "Can't chdir to /: $!";
2652 $pid = fork;
2653 setsid or die "Can't start a new session: $!";
2654 umask 0;
2655 } else {
2656 $pid = $$;
2657 }
2659 # Do something useful - put our PID into the pid_file
2660 if( 0 != $pid ) {
2661 open( LOCK_FILE, ">$pid_file" );
2662 print LOCK_FILE "$pid\n";
2663 close( LOCK_FILE );
2664 if( !$foreground ) {
2665 exit( 0 )
2666 };
2667 }
2669 # parse head url and revision from svn
2670 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2671 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2672 $server_headURL = defined $1 ? $1 : 'unknown' ;
2673 $server_revision = defined $2 ? $2 : 'unknown' ;
2674 if ($server_headURL =~ /\/tag\// ||
2675 $server_headURL =~ /\/branches\// ) {
2676 $server_status = "stable";
2677 } else {
2678 $server_status = "developmental" ;
2679 }
2682 daemon_log(" ", 1);
2683 daemon_log("$0 started!", 1);
2684 daemon_log("status: $server_status", 1);
2685 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2687 if ($no_bus > 0) {
2688 $bus_activ = "false"
2689 }
2691 # connect to incoming_db
2692 unlink($incoming_file_name);
2693 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2694 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2696 # connect to gosa-si job queue
2697 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2698 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2700 # connect to known_clients_db
2701 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2702 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2704 # connect to foreign_clients_db
2705 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2706 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2708 # connect to known_server_db
2709 unlink($known_server_file_name);
2710 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2711 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2713 # connect to login_usr_db
2714 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2715 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2717 # connect to fai_server_db and fai_release_db
2718 unlink($fai_server_file_name);
2719 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2720 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2722 unlink($fai_release_file_name);
2723 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2724 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2726 # connect to packages_list_db
2727 #unlink($packages_list_file_name);
2728 unlink($packages_list_under_construction);
2729 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2730 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2732 # connect to messaging_db
2733 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2734 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2737 # create xml object used for en/decrypting
2738 $xml = new XML::Simple();
2741 # foreign servers
2742 my @foreign_server_list;
2744 # add foreign server from cfg file
2745 if ($foreign_server_string ne "") {
2746 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2747 foreach my $foreign_server (@cfg_foreign_server_list) {
2748 push(@foreign_server_list, $foreign_server);
2749 }
2750 }
2752 # add foreign server from dns
2753 my @tmp_servers;
2754 if ( !$server_domain) {
2755 # Try our DNS Searchlist
2756 for my $domain(get_dns_domains()) {
2757 chomp($domain);
2758 my @tmp_domains= &get_server_addresses($domain);
2759 if(@tmp_domains) {
2760 for my $tmp_server(@tmp_domains) {
2761 push @tmp_servers, $tmp_server;
2762 }
2763 }
2764 }
2765 if(@tmp_servers && length(@tmp_servers)==0) {
2766 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2767 }
2768 } else {
2769 @tmp_servers = &get_server_addresses($server_domain);
2770 if( 0 == @tmp_servers ) {
2771 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2772 }
2773 }
2774 foreach my $server (@tmp_servers) {
2775 unshift(@foreign_server_list, $server);
2776 }
2777 # eliminate duplicate entries
2778 @foreign_server_list = &del_doubles(@foreign_server_list);
2779 my $all_foreign_server = join(", ", @foreign_server_list);
2780 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2782 # add all found foreign servers to known_server
2783 my $act_timestamp = &get_time();
2784 foreach my $foreign_server (@foreign_server_list) {
2785 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2786 primkey=>['hostname'],
2787 hostname=>$foreign_server,
2788 status=>'not_jet_registered',
2789 hostkey=>"none",
2790 timestamp=>$act_timestamp,
2791 } );
2792 }
2795 POE::Component::Server::TCP->new(
2796 Port => $server_port,
2797 ClientInput => sub {
2798 my ($kernel, $input) = @_[KERNEL, ARG0];
2799 push(@tasks, $input);
2800 push(@msgs_to_decrypt, $input);
2801 $kernel->yield("msg_to_decrypt");
2802 $kernel->yield("next_task");
2803 },
2804 InlineStates => {
2805 next_task => \&next_task,
2806 msg_to_decrypt => \&msg_to_decrypt,
2807 task_result => \&handle_task_result,
2808 task_done => \&handle_task_done,
2809 task_debug => \&handle_task_debug,
2810 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2811 }
2812 );
2814 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2816 # create session for repeatedly checking the job queue for jobs
2817 POE::Session->create(
2818 inline_states => {
2819 _start => \&_start,
2820 register_at_foreign_servers => \®ister_at_foreign_servers,
2821 sig_handler => \&sig_handler,
2822 watch_for_new_messages => \&watch_for_new_messages,
2823 watch_for_delivery_messages => \&watch_for_delivery_messages,
2824 watch_for_done_messages => \&watch_for_done_messages,
2825 watch_for_new_jobs => \&watch_for_new_jobs,
2826 watch_for_done_jobs => \&watch_for_done_jobs,
2827 create_packages_list_db => \&run_create_packages_list_db,
2828 create_fai_server_db => \&run_create_fai_server_db,
2829 create_fai_release_db => \&run_create_fai_release_db,
2830 session_run_result => \&session_run_result,
2831 session_run_debug => \&session_run_debug,
2832 session_run_done => \&session_run_done,
2833 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2834 }
2835 );
2838 # import all modules
2839 &import_modules;
2841 # TODO
2842 # check wether all modules are gosa-si valid passwd check
2846 POE::Kernel->run();
2847 exit;