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", "keylifetime");
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 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1343 }
1346 sub watch_for_done_jobs {
1347 my ($kernel,$heap) = @_[KERNEL, HEAP];
1349 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1350 " WHERE status='done'";
1351 my $res = $job_db->select_dbentry( $sql_statement );
1353 while( my ($id, $hit) = each %{$res} ) {
1354 my $jobdb_id = $hit->{id};
1355 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1356 my $res = $job_db->del_dbentry($sql_statement);
1357 }
1359 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1360 }
1363 sub watch_for_new_jobs {
1364 if($watch_for_new_jobs_in_progress == 0) {
1365 $watch_for_new_jobs_in_progress = 1;
1366 my ($kernel,$heap) = @_[KERNEL, HEAP];
1368 # check gosa job queue for jobs with executable timestamp
1369 my $timestamp = &get_time();
1370 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1371 my $res = $job_db->exec_statement( $sql_statement );
1373 # Merge all new jobs that would do the same actions
1374 my @drops;
1375 my $hits;
1376 foreach my $hit (reverse @{$res} ) {
1377 my $macaddress= lc @{$hit}[8];
1378 my $headertag= @{$hit}[5];
1379 if(
1380 defined($hits->{$macaddress}) &&
1381 defined($hits->{$macaddress}->{$headertag}) &&
1382 defined($hits->{$macaddress}->{$headertag}[0])
1383 ) {
1384 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1385 }
1386 $hits->{$macaddress}->{$headertag}= $hit;
1387 }
1389 # Delete new jobs with a matching job in state 'processing'
1390 foreach my $macaddress (keys %{$hits}) {
1391 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1392 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1393 if(defined($jobdb_id)) {
1394 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1395 my $res = $job_db->exec_statement( $sql_statement );
1396 foreach my $hit (@{$res}) {
1397 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1398 }
1399 } else {
1400 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1401 }
1402 }
1403 }
1405 # Commit deletion
1406 $job_db->exec_statementlist(\@drops);
1408 # Look for new jobs that could be executed
1409 foreach my $macaddress (keys %{$hits}) {
1411 # Look if there is an executing job
1412 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1413 my $res = $job_db->exec_statement( $sql_statement );
1415 # Skip new jobs for host if there is a processing job
1416 if(defined($res) and defined @{$res}[0]) {
1417 next;
1418 }
1420 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1421 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1422 if(defined($jobdb_id)) {
1423 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1425 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1426 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1427 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1429 # expect macaddress is unique!!!!!!
1430 my $target = $res_hash->{1}->{hostname};
1432 # change header
1433 $job_msg =~ s/<header>job_/<header>gosa_/;
1435 # add sqlite_id
1436 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1438 $job_msg =~ /<header>(\S+)<\/header>/;
1439 my $header = $1 ;
1440 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1442 # update status in job queue to 'processing'
1443 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1444 my $res = $job_db->update_dbentry($sql_statement);
1445 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1447 # We don't want parallel processing
1448 last;
1449 }
1450 }
1451 }
1453 $watch_for_new_jobs_in_progress = 0;
1454 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1455 }
1456 }
1459 sub watch_for_new_messages {
1460 my ($kernel,$heap) = @_[KERNEL, HEAP];
1461 my @coll_user_msg; # collection list of outgoing messages
1463 # check messaging_db for new incoming messages with executable timestamp
1464 my $timestamp = &get_time();
1465 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1466 my $res = $messaging_db->exec_statement( $sql_statement );
1467 foreach my $hit (@{$res}) {
1469 # create outgoing messages
1470 my $message_to = @{$hit}[3];
1471 # translate message_to to plain login name
1472 my @message_to_l = split(/,/, $message_to);
1473 my %receiver_h;
1474 foreach my $receiver (@message_to_l) {
1475 if ($receiver =~ /^u_([\s\S]*)$/) {
1476 $receiver_h{$1} = 0;
1477 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1478 my $group_name = $1;
1479 # fetch all group members from ldap and add them to receiver hash
1480 my $ldap_handle = &get_ldap_handle();
1481 if (defined $ldap_handle) {
1482 my $mesg = $ldap_handle->search(
1483 base => $ldap_base,
1484 scope => 'sub',
1485 attrs => ['memberUid'],
1486 filter => "cn=$group_name",
1487 );
1488 if ($mesg->count) {
1489 my @entries = $mesg->entries;
1490 foreach my $entry (@entries) {
1491 my @receivers= $entry->get_value("memberUid");
1492 foreach my $receiver (@receivers) {
1493 $receiver_h{$1} = 0;
1494 }
1495 }
1496 }
1497 # translating errors ?
1498 if ($mesg->code) {
1499 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1500 }
1501 # ldap handle error ?
1502 } else {
1503 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1504 }
1505 } else {
1506 my $sbjct = &encode_base64(@{$hit}[1]);
1507 my $msg = &encode_base64(@{$hit}[7]);
1508 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1509 }
1510 }
1511 my @receiver_l = keys(%receiver_h);
1513 my $message_id = @{$hit}[0];
1515 #add each outgoing msg to messaging_db
1516 my $receiver;
1517 foreach $receiver (@receiver_l) {
1518 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1519 "VALUES ('".
1520 $message_id."', '". # id
1521 @{$hit}[1]."', '". # subject
1522 @{$hit}[2]."', '". # message_from
1523 $receiver."', '". # message_to
1524 "none"."', '". # flag
1525 "out"."', '". # direction
1526 @{$hit}[6]."', '". # delivery_time
1527 @{$hit}[7]."', '". # message
1528 $timestamp."'". # timestamp
1529 ")";
1530 &daemon_log("M DEBUG: $sql_statement", 1);
1531 my $res = $messaging_db->exec_statement($sql_statement);
1532 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1533 }
1535 # set incoming message to flag d=deliverd
1536 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1537 &daemon_log("M DEBUG: $sql_statement", 7);
1538 $res = $messaging_db->update_dbentry($sql_statement);
1539 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1540 }
1542 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1543 return;
1544 }
1546 sub watch_for_delivery_messages {
1547 my ($kernel, $heap) = @_[KERNEL, HEAP];
1549 # select outgoing messages
1550 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1551 #&daemon_log("0 DEBUG: $sql", 7);
1552 my $res = $messaging_db->exec_statement( $sql_statement );
1554 # build out msg for each usr
1555 foreach my $hit (@{$res}) {
1556 my $receiver = @{$hit}[3];
1557 my $msg_id = @{$hit}[0];
1558 my $subject = @{$hit}[1];
1559 my $message = @{$hit}[7];
1561 # resolve usr -> host where usr is logged in
1562 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1563 #&daemon_log("0 DEBUG: $sql", 7);
1564 my $res = $login_users_db->exec_statement($sql);
1566 # reciver is logged in nowhere
1567 if (not ref(@$res[0]) eq "ARRAY") { next; }
1569 my $send_succeed = 0;
1570 foreach my $hit (@$res) {
1571 my $receiver_host = @$hit[0];
1572 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1574 # fetch key to encrypt msg propperly for usr/host
1575 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1576 &daemon_log("0 DEBUG: $sql", 7);
1577 my $res = $known_clients_db->select_dbentry($sql);
1579 # host is already down
1580 if (not ref(@$res[0]) eq "ARRAY") { next; }
1582 # host is on
1583 my $receiver_key = @{@{$res}[0]}[2];
1584 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1585 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1586 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1587 if ($error == 0 ) {
1588 $send_succeed++ ;
1589 }
1590 }
1592 if ($send_succeed) {
1593 # set outgoing msg at db to deliverd
1594 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1595 &daemon_log("0 DEBUG: $sql", 7);
1596 my $res = $messaging_db->exec_statement($sql);
1597 }
1598 }
1600 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1601 return;
1602 }
1605 sub watch_for_done_messages {
1606 my ($kernel,$heap) = @_[KERNEL, HEAP];
1608 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1609 #&daemon_log("0 DEBUG: $sql", 7);
1610 my $res = $messaging_db->exec_statement($sql);
1612 foreach my $hit (@{$res}) {
1613 my $msg_id = @{$hit}[0];
1615 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1616 #&daemon_log("0 DEBUG: $sql", 7);
1617 my $res = $messaging_db->exec_statement($sql);
1619 # not all usr msgs have been seen till now
1620 if ( ref(@$res[0]) eq "ARRAY") { next; }
1622 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1623 #&daemon_log("0 DEBUG: $sql", 7);
1624 $res = $messaging_db->exec_statement($sql);
1626 }
1628 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1629 return;
1630 }
1633 sub watch_for_old_known_clients {
1634 my ($kernel,$heap) = @_[KERNEL, HEAP];
1636 my $sql_statement = "SELECT * FROM $known_clients_tn";
1637 my $res = $known_clients_db->select_dbentry( $sql_statement );
1639 my $act_time = int(&get_time());
1640 while ( my ($hit_num, $hit) = each %$res) {
1641 my $expired_timestamp = int($hit->{'timestamp'}) + (2 * int($hit->{'keylifetime'}));
1642 if ($act_time > $expired_timestamp) {
1643 my $hostname = $hit->{'hostname'};
1644 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1645 my $del_res = $known_clients_db->exec_statement($del_sql);
1647 &main::daemon_log("0 INFO: timestamp of client '$hostname' is expired, client will be deleted from known_clients_db", 5);
1648 }
1650 }
1652 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1653 }
1656 sub get_ldap_handle {
1657 my ($session_id) = @_;
1658 my $heap;
1659 my $ldap_handle;
1661 if (not defined $session_id ) { $session_id = 0 };
1662 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1664 if ($session_id == 0) {
1665 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1666 $ldap_handle = Net::LDAP->new( $ldap_uri );
1667 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password);
1669 } else {
1670 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1671 if( defined $session_reference ) {
1672 $heap = $session_reference->get_heap();
1673 }
1675 if (not defined $heap) {
1676 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1677 return;
1678 }
1680 # TODO: This "if" is nonsense, because it doesn't prove that the
1681 # used handle is still valid - or if we've to reconnect...
1682 #if (not exists $heap->{ldap_handle}) {
1683 $ldap_handle = Net::LDAP->new( $ldap_uri );
1684 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1685 $heap->{ldap_handle} = $ldap_handle;
1686 #}
1687 }
1688 return $ldap_handle;
1689 }
1692 sub change_fai_state {
1693 my ($st, $targets, $session_id) = @_;
1694 $session_id = 0 if not defined $session_id;
1695 # Set FAI state to localboot
1696 my %mapActions= (
1697 reboot => '',
1698 update => 'softupdate',
1699 localboot => 'localboot',
1700 reinstall => 'install',
1701 rescan => '',
1702 wake => '',
1703 memcheck => 'memcheck',
1704 sysinfo => 'sysinfo',
1705 install => 'install',
1706 );
1708 # Return if this is unknown
1709 if (!exists $mapActions{ $st }){
1710 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1711 return;
1712 }
1714 my $state= $mapActions{ $st };
1716 my $ldap_handle = &get_ldap_handle($session_id);
1717 if( defined($ldap_handle) ) {
1719 # Build search filter for hosts
1720 my $search= "(&(objectClass=GOhard)";
1721 foreach (@{$targets}){
1722 $search.= "(macAddress=$_)";
1723 }
1724 $search.= ")";
1726 # If there's any host inside of the search string, procress them
1727 if (!($search =~ /macAddress/)){
1728 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1729 return;
1730 }
1732 # Perform search for Unit Tag
1733 my $mesg = $ldap_handle->search(
1734 base => $ldap_base,
1735 scope => 'sub',
1736 attrs => ['dn', 'FAIstate', 'objectClass'],
1737 filter => "$search"
1738 );
1740 if ($mesg->count) {
1741 my @entries = $mesg->entries;
1742 foreach my $entry (@entries) {
1743 # Only modify entry if it is not set to '$state'
1744 if ($entry->get_value("FAIstate") ne "$state"){
1745 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1746 my $result;
1747 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1748 if (exists $tmp{'FAIobject'}){
1749 if ($state eq ''){
1750 $result= $ldap_handle->modify($entry->dn, changes => [
1751 delete => [ FAIstate => [] ] ]);
1752 } else {
1753 $result= $ldap_handle->modify($entry->dn, changes => [
1754 replace => [ FAIstate => $state ] ]);
1755 }
1756 } elsif ($state ne ''){
1757 $result= $ldap_handle->modify($entry->dn, changes => [
1758 add => [ objectClass => 'FAIobject' ],
1759 add => [ FAIstate => $state ] ]);
1760 }
1762 # Errors?
1763 if ($result->code){
1764 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1765 }
1766 } else {
1767 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1768 }
1769 }
1770 }
1771 # if no ldap handle defined
1772 } else {
1773 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1774 }
1776 }
1779 sub change_goto_state {
1780 my ($st, $targets, $session_id) = @_;
1781 $session_id = 0 if not defined $session_id;
1783 # Switch on or off?
1784 my $state= $st eq 'active' ? 'active': 'locked';
1786 my $ldap_handle = &get_ldap_handle($session_id);
1787 if( defined($ldap_handle) ) {
1789 # Build search filter for hosts
1790 my $search= "(&(objectClass=GOhard)";
1791 foreach (@{$targets}){
1792 $search.= "(macAddress=$_)";
1793 }
1794 $search.= ")";
1796 # If there's any host inside of the search string, procress them
1797 if (!($search =~ /macAddress/)){
1798 return;
1799 }
1801 # Perform search for Unit Tag
1802 my $mesg = $ldap_handle->search(
1803 base => $ldap_base,
1804 scope => 'sub',
1805 attrs => ['dn', 'gotoMode'],
1806 filter => "$search"
1807 );
1809 if ($mesg->count) {
1810 my @entries = $mesg->entries;
1811 foreach my $entry (@entries) {
1813 # Only modify entry if it is not set to '$state'
1814 if ($entry->get_value("gotoMode") ne $state){
1816 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1817 my $result;
1818 $result= $ldap_handle->modify($entry->dn, changes => [
1819 replace => [ gotoMode => $state ] ]);
1821 # Errors?
1822 if ($result->code){
1823 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1824 }
1826 }
1827 }
1828 }
1830 }
1831 }
1834 sub run_create_fai_server_db {
1835 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1836 my $session_id = $session->ID;
1837 my $task = POE::Wheel::Run->new(
1838 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1839 StdoutEvent => "session_run_result",
1840 StderrEvent => "session_run_debug",
1841 CloseEvent => "session_run_done",
1842 );
1844 $heap->{task}->{ $task->ID } = $task;
1845 return;
1846 }
1849 sub create_fai_server_db {
1850 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1851 my $result;
1853 if (not defined $session_id) { $session_id = 0; }
1854 my $ldap_handle = &get_ldap_handle();
1855 if(defined($ldap_handle)) {
1856 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1857 my $mesg= $ldap_handle->search(
1858 base => $ldap_base,
1859 scope => 'sub',
1860 attrs => ['FAIrepository', 'gosaUnitTag'],
1861 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1862 );
1863 if($mesg->{'resultCode'} == 0 &&
1864 $mesg->count != 0) {
1865 foreach my $entry (@{$mesg->{entries}}) {
1866 if($entry->exists('FAIrepository')) {
1867 # Add an entry for each Repository configured for server
1868 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1869 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1870 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1871 $result= $fai_server_db->add_dbentry( {
1872 table => $table_name,
1873 primkey => ['server', 'release', 'tag'],
1874 server => $tmp_url,
1875 release => $tmp_release,
1876 sections => $tmp_sections,
1877 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1878 } );
1879 }
1880 }
1881 }
1882 }
1883 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1885 # TODO: Find a way to post the 'create_packages_list_db' event
1886 if(not defined($dont_create_packages_list)) {
1887 &create_packages_list_db(undef, undef, $session_id);
1888 }
1889 }
1891 $ldap_handle->disconnect;
1892 return $result;
1893 }
1896 sub run_create_fai_release_db {
1897 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1898 my $session_id = $session->ID;
1899 my $task = POE::Wheel::Run->new(
1900 Program => sub { &create_fai_release_db($table_name, $session_id) },
1901 StdoutEvent => "session_run_result",
1902 StderrEvent => "session_run_debug",
1903 CloseEvent => "session_run_done",
1904 );
1906 $heap->{task}->{ $task->ID } = $task;
1907 return;
1908 }
1911 sub create_fai_release_db {
1912 my ($table_name, $session_id) = @_;
1913 my $result;
1915 # used for logging
1916 if (not defined $session_id) { $session_id = 0; }
1918 my $ldap_handle = &get_ldap_handle();
1919 if(defined($ldap_handle)) {
1920 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1921 my $mesg= $ldap_handle->search(
1922 base => $ldap_base,
1923 scope => 'sub',
1924 attrs => [],
1925 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1926 );
1927 if($mesg->{'resultCode'} == 0 &&
1928 $mesg->count != 0) {
1929 # Walk through all possible FAI container ou's
1930 my @sql_list;
1931 my $timestamp= &get_time();
1932 foreach my $ou (@{$mesg->{entries}}) {
1933 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1934 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1935 my @tmp_array=get_fai_release_entries($tmp_classes);
1936 if(@tmp_array) {
1937 foreach my $entry (@tmp_array) {
1938 if(defined($entry) && ref($entry) eq 'HASH') {
1939 my $sql=
1940 "INSERT INTO $table_name "
1941 ."(timestamp, release, class, type, state) VALUES ("
1942 .$timestamp.","
1943 ."'".$entry->{'release'}."',"
1944 ."'".$entry->{'class'}."',"
1945 ."'".$entry->{'type'}."',"
1946 ."'".$entry->{'state'}."')";
1947 push @sql_list, $sql;
1948 }
1949 }
1950 }
1951 }
1952 }
1954 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1955 if(@sql_list) {
1956 unshift @sql_list, "VACUUM";
1957 unshift @sql_list, "DELETE FROM $table_name";
1958 $fai_release_db->exec_statementlist(\@sql_list);
1959 }
1960 daemon_log("$session_id DEBUG: Done with inserting",7);
1961 }
1962 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1963 }
1964 $ldap_handle->disconnect;
1965 return $result;
1966 }
1968 sub get_fai_types {
1969 my $tmp_classes = shift || return undef;
1970 my @result;
1972 foreach my $type(keys %{$tmp_classes}) {
1973 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1974 my $entry = {
1975 type => $type,
1976 state => $tmp_classes->{$type}[0],
1977 };
1978 push @result, $entry;
1979 }
1980 }
1982 return @result;
1983 }
1985 sub get_fai_state {
1986 my $result = "";
1987 my $tmp_classes = shift || return $result;
1989 foreach my $type(keys %{$tmp_classes}) {
1990 if(defined($tmp_classes->{$type}[0])) {
1991 $result = $tmp_classes->{$type}[0];
1993 # State is equal for all types in class
1994 last;
1995 }
1996 }
1998 return $result;
1999 }
2001 sub resolve_fai_classes {
2002 my ($fai_base, $ldap_handle, $session_id) = @_;
2003 if (not defined $session_id) { $session_id = 0; }
2004 my $result;
2005 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2006 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2007 my $fai_classes;
2009 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2010 my $mesg= $ldap_handle->search(
2011 base => $fai_base,
2012 scope => 'sub',
2013 attrs => ['cn','objectClass','FAIstate'],
2014 filter => $fai_filter,
2015 );
2016 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2018 if($mesg->{'resultCode'} == 0 &&
2019 $mesg->count != 0) {
2020 foreach my $entry (@{$mesg->{entries}}) {
2021 if($entry->exists('cn')) {
2022 my $tmp_dn= $entry->dn();
2024 # Skip classname and ou dn parts for class
2025 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2027 # Skip classes without releases
2028 if((!defined($tmp_release)) || length($tmp_release)==0) {
2029 next;
2030 }
2032 my $tmp_cn= $entry->get_value('cn');
2033 my $tmp_state= $entry->get_value('FAIstate');
2035 my $tmp_type;
2036 # Get FAI type
2037 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2038 if(grep $_ eq $oclass, @possible_fai_classes) {
2039 $tmp_type= $oclass;
2040 last;
2041 }
2042 }
2044 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2045 # A Subrelease
2046 my @sub_releases = split(/,/, $tmp_release);
2048 # Walk through subreleases and build hash tree
2049 my $hash;
2050 while(my $tmp_sub_release = pop @sub_releases) {
2051 $hash .= "\{'$tmp_sub_release'\}->";
2052 }
2053 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2054 } else {
2055 # A branch, no subrelease
2056 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2057 }
2058 } elsif (!$entry->exists('cn')) {
2059 my $tmp_dn= $entry->dn();
2060 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2062 # Skip classes without releases
2063 if((!defined($tmp_release)) || length($tmp_release)==0) {
2064 next;
2065 }
2067 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2068 # A Subrelease
2069 my @sub_releases= split(/,/, $tmp_release);
2071 # Walk through subreleases and build hash tree
2072 my $hash;
2073 while(my $tmp_sub_release = pop @sub_releases) {
2074 $hash .= "\{'$tmp_sub_release'\}->";
2075 }
2076 # Remove the last two characters
2077 chop($hash);
2078 chop($hash);
2080 eval('$fai_classes->'.$hash.'= {}');
2081 } else {
2082 # A branch, no subrelease
2083 if(!exists($fai_classes->{$tmp_release})) {
2084 $fai_classes->{$tmp_release} = {};
2085 }
2086 }
2087 }
2088 }
2090 # The hash is complete, now we can honor the copy-on-write based missing entries
2091 foreach my $release (keys %$fai_classes) {
2092 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2093 }
2094 }
2095 return $result;
2096 }
2098 sub apply_fai_inheritance {
2099 my $fai_classes = shift || return {};
2100 my $tmp_classes;
2102 # Get the classes from the branch
2103 foreach my $class (keys %{$fai_classes}) {
2104 # Skip subreleases
2105 if($class =~ /^ou=.*$/) {
2106 next;
2107 } else {
2108 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2109 }
2110 }
2112 # Apply to each subrelease
2113 foreach my $subrelease (keys %{$fai_classes}) {
2114 if($subrelease =~ /ou=/) {
2115 foreach my $tmp_class (keys %{$tmp_classes}) {
2116 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2117 $fai_classes->{$subrelease}->{$tmp_class} =
2118 deep_copy($tmp_classes->{$tmp_class});
2119 } else {
2120 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2121 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2122 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2123 deep_copy($tmp_classes->{$tmp_class}->{$type});
2124 }
2125 }
2126 }
2127 }
2128 }
2129 }
2131 # Find subreleases in deeper levels
2132 foreach my $subrelease (keys %{$fai_classes}) {
2133 if($subrelease =~ /ou=/) {
2134 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2135 if($subsubrelease =~ /ou=/) {
2136 apply_fai_inheritance($fai_classes->{$subrelease});
2137 }
2138 }
2139 }
2140 }
2142 return $fai_classes;
2143 }
2145 sub get_fai_release_entries {
2146 my $tmp_classes = shift || return;
2147 my $parent = shift || "";
2148 my @result = shift || ();
2150 foreach my $entry (keys %{$tmp_classes}) {
2151 if(defined($entry)) {
2152 if($entry =~ /^ou=.*$/) {
2153 my $release_name = $entry;
2154 $release_name =~ s/ou=//g;
2155 if(length($parent)>0) {
2156 $release_name = $parent."/".$release_name;
2157 }
2158 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2159 foreach my $bufentry(@bufentries) {
2160 push @result, $bufentry;
2161 }
2162 } else {
2163 my @types = get_fai_types($tmp_classes->{$entry});
2164 foreach my $type (@types) {
2165 push @result,
2166 {
2167 'class' => $entry,
2168 'type' => $type->{'type'},
2169 'release' => $parent,
2170 'state' => $type->{'state'},
2171 };
2172 }
2173 }
2174 }
2175 }
2177 return @result;
2178 }
2180 sub deep_copy {
2181 my $this = shift;
2182 if (not ref $this) {
2183 $this;
2184 } elsif (ref $this eq "ARRAY") {
2185 [map deep_copy($_), @$this];
2186 } elsif (ref $this eq "HASH") {
2187 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2188 } else { die "what type is $_?" }
2189 }
2192 sub session_run_result {
2193 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2194 $kernel->sig(CHLD => "child_reap");
2195 }
2197 sub session_run_debug {
2198 my $result = $_[ARG0];
2199 print STDERR "$result\n";
2200 }
2202 sub session_run_done {
2203 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2204 delete $heap->{task}->{$task_id};
2205 }
2208 sub create_sources_list {
2209 my $session_id = shift;
2210 my $ldap_handle = &main::get_ldap_handle;
2211 my $result="/tmp/gosa_si_tmp_sources_list";
2213 # Remove old file
2214 if(stat($result)) {
2215 unlink($result);
2216 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2217 }
2219 my $fh;
2220 open($fh, ">$result");
2221 if (not defined $fh) {
2222 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2223 return undef;
2224 }
2225 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2226 my $mesg=$ldap_handle->search(
2227 base => $main::ldap_server_dn,
2228 scope => 'base',
2229 attrs => 'FAIrepository',
2230 filter => 'objectClass=FAIrepositoryServer'
2231 );
2232 if($mesg->count) {
2233 foreach my $entry(@{$mesg->{'entries'}}) {
2234 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2235 my ($server, $tag, $release, $sections)= split /\|/, $value;
2236 my $line = "deb $server $release";
2237 $sections =~ s/,/ /g;
2238 $line.= " $sections";
2239 print $fh $line."\n";
2240 }
2241 }
2242 }
2243 } else {
2244 if (defined $main::ldap_server_dn){
2245 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2246 } else {
2247 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2248 }
2249 }
2250 close($fh);
2252 return $result;
2253 }
2256 sub run_create_packages_list_db {
2257 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2258 my $session_id = $session->ID;
2260 my $task = POE::Wheel::Run->new(
2261 Priority => +20,
2262 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2263 StdoutEvent => "session_run_result",
2264 StderrEvent => "session_run_debug",
2265 CloseEvent => "session_run_done",
2266 );
2267 $heap->{task}->{ $task->ID } = $task;
2268 }
2271 sub create_packages_list_db {
2272 my ($ldap_handle, $sources_file, $session_id) = @_;
2274 # it should not be possible to trigger a recreation of packages_list_db
2275 # while packages_list_db is under construction, so set flag packages_list_under_construction
2276 # which is tested befor recreation can be started
2277 if (-r $packages_list_under_construction) {
2278 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2279 return;
2280 } else {
2281 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2282 # set packages_list_under_construction to true
2283 system("touch $packages_list_under_construction");
2284 @packages_list_statements=();
2285 }
2287 if (not defined $session_id) { $session_id = 0; }
2288 if (not defined $ldap_handle) {
2289 $ldap_handle= &get_ldap_handle();
2291 if (not defined $ldap_handle) {
2292 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2293 unlink($packages_list_under_construction);
2294 return;
2295 }
2296 }
2297 if (not defined $sources_file) {
2298 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2299 $sources_file = &create_sources_list($session_id);
2300 }
2302 if (not defined $sources_file) {
2303 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2304 unlink($packages_list_under_construction);
2305 return;
2306 }
2308 my $line;
2310 open(CONFIG, "<$sources_file") or do {
2311 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2312 unlink($packages_list_under_construction);
2313 return;
2314 };
2316 # Read lines
2317 while ($line = <CONFIG>){
2318 # Unify
2319 chop($line);
2320 $line =~ s/^\s+//;
2321 $line =~ s/^\s+/ /;
2323 # Strip comments
2324 $line =~ s/#.*$//g;
2326 # Skip empty lines
2327 if ($line =~ /^\s*$/){
2328 next;
2329 }
2331 # Interpret deb line
2332 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2333 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2334 my $section;
2335 foreach $section (split(' ', $sections)){
2336 &parse_package_info( $baseurl, $dist, $section, $session_id );
2337 }
2338 }
2339 }
2341 close (CONFIG);
2343 find(\&cleanup_and_extract, keys( %repo_dirs ));
2344 &main::strip_packages_list_statements();
2345 unshift @packages_list_statements, "VACUUM";
2346 $packages_list_db->exec_statementlist(\@packages_list_statements);
2347 unlink($packages_list_under_construction);
2348 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2349 return;
2350 }
2352 # This function should do some intensive task to minimize the db-traffic
2353 sub strip_packages_list_statements {
2354 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2355 my @new_statement_list=();
2356 my $hash;
2357 my $insert_hash;
2358 my $update_hash;
2359 my $delete_hash;
2360 my $local_timestamp=get_time();
2362 foreach my $existing_entry (@existing_entries) {
2363 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2364 }
2366 foreach my $statement (@packages_list_statements) {
2367 if($statement =~ /^INSERT/i) {
2368 # Assign the values from the insert statement
2369 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2370 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2371 if(exists($hash->{$distribution}->{$package}->{$version})) {
2372 # If section or description has changed, update the DB
2373 if(
2374 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2375 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2376 ) {
2377 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2378 }
2379 } else {
2380 # Insert a non-existing entry to db
2381 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2382 }
2383 } elsif ($statement =~ /^UPDATE/i) {
2384 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2385 /^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;
2386 foreach my $distribution (keys %{$hash}) {
2387 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2388 # update the insertion hash to execute only one query per package (insert instead insert+update)
2389 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2390 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2391 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2392 my $section;
2393 my $description;
2394 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2395 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2396 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2397 }
2398 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2399 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2400 }
2401 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2402 }
2403 }
2404 }
2405 }
2406 }
2408 # TODO: Check for orphaned entries
2410 # unroll the insert_hash
2411 foreach my $distribution (keys %{$insert_hash}) {
2412 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2413 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2414 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2415 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2416 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2417 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2418 ."'$local_timestamp')";
2419 }
2420 }
2421 }
2423 # unroll the update hash
2424 foreach my $distribution (keys %{$update_hash}) {
2425 foreach my $package (keys %{$update_hash->{$distribution}}) {
2426 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2427 my $set = "";
2428 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2429 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2430 }
2431 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2432 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2433 }
2434 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2435 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2436 }
2437 if(defined($set) and length($set) > 0) {
2438 $set .= "timestamp = '$local_timestamp'";
2439 } else {
2440 next;
2441 }
2442 push @new_statement_list,
2443 "UPDATE $main::packages_list_tn SET $set WHERE"
2444 ." distribution = '$distribution'"
2445 ." AND package = '$package'"
2446 ." AND version = '$version'";
2447 }
2448 }
2449 }
2451 @packages_list_statements = @new_statement_list;
2452 }
2455 sub parse_package_info {
2456 my ($baseurl, $dist, $section, $session_id)= @_;
2457 my ($package);
2458 if (not defined $session_id) { $session_id = 0; }
2459 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2460 $repo_dirs{ "${repo_path}/pool" } = 1;
2462 foreach $package ("Packages.gz"){
2463 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2464 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2465 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2466 }
2468 }
2471 sub get_package {
2472 my ($url, $dest, $session_id)= @_;
2473 if (not defined $session_id) { $session_id = 0; }
2475 my $tpath = dirname($dest);
2476 -d "$tpath" || mkpath "$tpath";
2478 # This is ugly, but I've no time to take a look at "how it works in perl"
2479 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2480 system("gunzip -cd '$dest' > '$dest.in'");
2481 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2482 unlink($dest);
2483 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2484 } else {
2485 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2486 }
2487 return 0;
2488 }
2491 sub parse_package {
2492 my ($path, $dist, $srv_path, $session_id)= @_;
2493 if (not defined $session_id) { $session_id = 0;}
2494 my ($package, $version, $section, $description);
2495 my $PACKAGES;
2496 my $timestamp = &get_time();
2498 if(not stat("$path.in")) {
2499 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2500 return;
2501 }
2503 open($PACKAGES, "<$path.in");
2504 if(not defined($PACKAGES)) {
2505 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2506 return;
2507 }
2509 # Read lines
2510 while (<$PACKAGES>){
2511 my $line = $_;
2512 # Unify
2513 chop($line);
2515 # Use empty lines as a trigger
2516 if ($line =~ /^\s*$/){
2517 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2518 push(@packages_list_statements, $sql);
2519 $package = "none";
2520 $version = "none";
2521 $section = "none";
2522 $description = "none";
2523 next;
2524 }
2526 # Trigger for package name
2527 if ($line =~ /^Package:\s/){
2528 ($package)= ($line =~ /^Package: (.*)$/);
2529 next;
2530 }
2532 # Trigger for version
2533 if ($line =~ /^Version:\s/){
2534 ($version)= ($line =~ /^Version: (.*)$/);
2535 next;
2536 }
2538 # Trigger for description
2539 if ($line =~ /^Description:\s/){
2540 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2541 next;
2542 }
2544 # Trigger for section
2545 if ($line =~ /^Section:\s/){
2546 ($section)= ($line =~ /^Section: (.*)$/);
2547 next;
2548 }
2550 # Trigger for filename
2551 if ($line =~ /^Filename:\s/){
2552 my ($filename) = ($line =~ /^Filename: (.*)$/);
2553 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2554 next;
2555 }
2556 }
2558 close( $PACKAGES );
2559 unlink( "$path.in" );
2560 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2561 }
2564 sub store_fileinfo {
2565 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2567 my %fileinfo = (
2568 'package' => $package,
2569 'dist' => $dist,
2570 'version' => $vers,
2571 );
2573 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2574 }
2577 sub cleanup_and_extract {
2578 my $fileinfo = $repo_files{ $File::Find::name };
2580 if( defined $fileinfo ) {
2582 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2583 my $sql;
2584 my $package = $fileinfo->{ 'package' };
2585 my $newver = $fileinfo->{ 'version' };
2587 mkpath($dir);
2588 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2590 if( -f "$dir/DEBIAN/templates" ) {
2592 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2594 my $tmpl= "";
2595 {
2596 local $/=undef;
2597 open FILE, "$dir/DEBIAN/templates";
2598 $tmpl = &encode_base64(<FILE>);
2599 close FILE;
2600 }
2601 rmtree("$dir/DEBIAN/templates");
2603 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2604 push @packages_list_statements, $sql;
2605 }
2606 }
2608 return;
2609 }
2612 sub register_at_foreign_servers {
2613 my ($kernel) = $_[KERNEL];
2615 # hole alle bekannten server aus known_server_db
2616 my $server_sql = "SELECT * FROM $known_server_tn";
2617 my $server_res = $known_server_db->exec_statement($server_sql);
2619 # no entries in known_server_db
2620 if (not ref(@$server_res[0]) eq "ARRAY") {
2621 # TODO
2622 }
2624 # detect already connected clients
2625 my $client_sql = "SELECT * FROM $known_clients_tn";
2626 my $client_res = $known_clients_db->exec_statement($client_sql);
2628 # send my server details to all other gosa-si-server within the network
2629 foreach my $hit (@$server_res) {
2630 my $hostname = @$hit[0];
2631 my $hostkey = &create_passwd;
2633 # add already connected clients to registration message
2634 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2635 &add_content2xml_hash($myhash, 'key', $hostkey);
2636 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2638 # build registration message and send it
2639 my $foreign_server_msg = &create_xml_string($myhash);
2640 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2641 }
2643 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2644 return;
2645 }
2648 #==== MAIN = main ==============================================================
2649 # parse commandline options
2650 Getopt::Long::Configure( "bundling" );
2651 GetOptions("h|help" => \&usage,
2652 "c|config=s" => \$cfg_file,
2653 "f|foreground" => \$foreground,
2654 "v|verbose+" => \$verbose,
2655 "no-bus+" => \$no_bus,
2656 "no-arp+" => \$no_arp,
2657 );
2659 # read and set config parameters
2660 &check_cmdline_param ;
2661 &read_configfile;
2662 &check_pid;
2664 $SIG{CHLD} = 'IGNORE';
2666 # forward error messages to logfile
2667 if( ! $foreground ) {
2668 open( STDIN, '+>/dev/null' );
2669 open( STDOUT, '+>&STDIN' );
2670 open( STDERR, '+>&STDIN' );
2671 }
2673 # Just fork, if we are not in foreground mode
2674 if( ! $foreground ) {
2675 chdir '/' or die "Can't chdir to /: $!";
2676 $pid = fork;
2677 setsid or die "Can't start a new session: $!";
2678 umask 0;
2679 } else {
2680 $pid = $$;
2681 }
2683 # Do something useful - put our PID into the pid_file
2684 if( 0 != $pid ) {
2685 open( LOCK_FILE, ">$pid_file" );
2686 print LOCK_FILE "$pid\n";
2687 close( LOCK_FILE );
2688 if( !$foreground ) {
2689 exit( 0 )
2690 };
2691 }
2693 # parse head url and revision from svn
2694 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2695 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2696 $server_headURL = defined $1 ? $1 : 'unknown' ;
2697 $server_revision = defined $2 ? $2 : 'unknown' ;
2698 if ($server_headURL =~ /\/tag\// ||
2699 $server_headURL =~ /\/branches\// ) {
2700 $server_status = "stable";
2701 } else {
2702 $server_status = "developmental" ;
2703 }
2706 daemon_log(" ", 1);
2707 daemon_log("$0 started!", 1);
2708 daemon_log("status: $server_status", 1);
2709 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2711 if ($no_bus > 0) {
2712 $bus_activ = "false"
2713 }
2715 # connect to incoming_db
2716 unlink($incoming_file_name);
2717 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2718 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2720 # connect to gosa-si job queue
2721 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2722 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2724 # connect to known_clients_db
2725 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2726 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2728 # connect to foreign_clients_db
2729 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2730 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2732 # connect to known_server_db
2733 unlink($known_server_file_name);
2734 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2735 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2737 # connect to login_usr_db
2738 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2739 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2741 # connect to fai_server_db and fai_release_db
2742 unlink($fai_server_file_name);
2743 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2744 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2746 unlink($fai_release_file_name);
2747 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2748 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2750 # connect to packages_list_db
2751 #unlink($packages_list_file_name);
2752 unlink($packages_list_under_construction);
2753 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2754 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2756 # connect to messaging_db
2757 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2758 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2761 # create xml object used for en/decrypting
2762 $xml = new XML::Simple();
2765 # foreign servers
2766 my @foreign_server_list;
2768 # add foreign server from cfg file
2769 if ($foreign_server_string ne "") {
2770 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2771 foreach my $foreign_server (@cfg_foreign_server_list) {
2772 push(@foreign_server_list, $foreign_server);
2773 }
2774 }
2776 # add foreign server from dns
2777 my @tmp_servers;
2778 if ( !$server_domain) {
2779 # Try our DNS Searchlist
2780 for my $domain(get_dns_domains()) {
2781 chomp($domain);
2782 my @tmp_domains= &get_server_addresses($domain);
2783 if(@tmp_domains) {
2784 for my $tmp_server(@tmp_domains) {
2785 push @tmp_servers, $tmp_server;
2786 }
2787 }
2788 }
2789 if(@tmp_servers && length(@tmp_servers)==0) {
2790 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2791 }
2792 } else {
2793 @tmp_servers = &get_server_addresses($server_domain);
2794 if( 0 == @tmp_servers ) {
2795 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2796 }
2797 }
2798 foreach my $server (@tmp_servers) {
2799 unshift(@foreign_server_list, $server);
2800 }
2801 # eliminate duplicate entries
2802 @foreign_server_list = &del_doubles(@foreign_server_list);
2803 my $all_foreign_server = join(", ", @foreign_server_list);
2804 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2806 # add all found foreign servers to known_server
2807 my $act_timestamp = &get_time();
2808 foreach my $foreign_server (@foreign_server_list) {
2809 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2810 primkey=>['hostname'],
2811 hostname=>$foreign_server,
2812 status=>'not_jet_registered',
2813 hostkey=>"none",
2814 timestamp=>$act_timestamp,
2815 } );
2816 }
2819 POE::Component::Server::TCP->new(
2820 Port => $server_port,
2821 ClientInput => sub {
2822 my ($kernel, $input) = @_[KERNEL, ARG0];
2823 push(@tasks, $input);
2824 push(@msgs_to_decrypt, $input);
2825 $kernel->yield("msg_to_decrypt");
2826 $kernel->yield("next_task");
2827 },
2828 InlineStates => {
2829 next_task => \&next_task,
2830 msg_to_decrypt => \&msg_to_decrypt,
2831 task_result => \&handle_task_result,
2832 task_done => \&handle_task_done,
2833 task_debug => \&handle_task_debug,
2834 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2835 }
2836 );
2838 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2840 # create session for repeatedly checking the job queue for jobs
2841 POE::Session->create(
2842 inline_states => {
2843 _start => \&_start,
2844 register_at_foreign_servers => \®ister_at_foreign_servers,
2845 sig_handler => \&sig_handler,
2846 watch_for_new_messages => \&watch_for_new_messages,
2847 watch_for_delivery_messages => \&watch_for_delivery_messages,
2848 watch_for_done_messages => \&watch_for_done_messages,
2849 watch_for_new_jobs => \&watch_for_new_jobs,
2850 watch_for_done_jobs => \&watch_for_done_jobs,
2851 watch_for_old_known_clients => \&watch_for_old_known_clients,
2852 create_packages_list_db => \&run_create_packages_list_db,
2853 create_fai_server_db => \&run_create_fai_server_db,
2854 create_fai_release_db => \&run_create_fai_release_db,
2855 session_run_result => \&session_run_result,
2856 session_run_debug => \&session_run_debug,
2857 session_run_done => \&session_run_done,
2858 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2859 }
2860 );
2863 # import all modules
2864 &import_modules;
2866 # TODO
2867 # check wether all modules are gosa-si valid passwd check
2871 POE::Kernel->run();
2872 exit;