c3d08010164bb723fc71938d107e0d0a91ff5e2b
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($procid, $pid);
76 my ($arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $gosa_address;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # specifies the verbosity of the daemon_log
103 $verbose = 0 ;
105 # if foreground is not null, script will be not forked to background
106 $foreground = 0 ;
108 # specifies the timeout seconds while checking the online status of a registrating client
109 $ping_timeout = 5;
111 $no_arp = 0;
112 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
113 my @packages_list_statements;
114 my $watch_for_new_jobs_in_progress = 0;
116 # holds all incoming decrypted messages
117 our $incoming_db;
118 our $incoming_tn = 'incoming';
119 my $incoming_file_name;
120 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
121 "timestamp DEFAULT 'none'",
122 "headertag DEFAULT 'none'",
123 "targettag DEFAULT 'none'",
124 "xmlmessage DEFAULT 'none'",
125 "module DEFAULT 'none'",
126 "sessionid DEFAULT '0'",
127 );
129 # holds all gosa jobs
130 our $job_db;
131 our $job_queue_tn = 'jobs';
132 my $job_queue_file_name;
133 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
134 "timestamp DEFAULT 'none'",
135 "status DEFAULT 'none'",
136 "result DEFAULT 'none'",
137 "progress DEFAULT 'none'",
138 "headertag DEFAULT 'none'",
139 "targettag DEFAULT 'none'",
140 "xmlmessage DEFAULT 'none'",
141 "macaddress DEFAULT 'none'",
142 "plainname DEFAULT 'none'",
143 );
145 # holds all other gosa-si-server
146 our $known_server_db;
147 our $known_server_tn = "known_server";
148 my $known_server_file_name;
149 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
151 # holds all registrated clients
152 our $known_clients_db;
153 our $known_clients_tn = "known_clients";
154 my $known_clients_file_name;
155 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
157 # holds all registered clients at a foreign server
158 our $foreign_clients_db;
159 our $foreign_clients_tn = "foreign_clients";
160 my $foreign_clients_file_name;
161 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
163 # holds all logged in user at each client
164 our $login_users_db;
165 our $login_users_tn = "login_users";
166 my $login_users_file_name;
167 my @login_users_col_names = ("client", "user", "timestamp");
169 # holds all fai server, the debian release and tag
170 our $fai_server_db;
171 our $fai_server_tn = "fai_server";
172 my $fai_server_file_name;
173 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
175 our $fai_release_db;
176 our $fai_release_tn = "fai_release";
177 my $fai_release_file_name;
178 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
180 # holds all packages available from different repositories
181 our $packages_list_db;
182 our $packages_list_tn = "packages_list";
183 my $packages_list_file_name;
184 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
185 my $outdir = "/tmp/packages_list_db";
186 my $arch = "i386";
188 # holds all messages which should be delivered to a user
189 our $messaging_db;
190 our $messaging_tn = "messaging";
191 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
192 "flag", "direction", "delivery_time", "message", "timestamp" );
193 my $messaging_file_name;
195 # path to directory to store client install log files
196 our $client_fai_log_dir = "/var/log/fai";
198 # queue which stores taskes until one of the $max_children children are ready to process the task
199 my @tasks = qw();
200 my @msgs_to_decrypt = qw();
201 my $max_children = 2;
204 %cfg_defaults = (
205 "general" => {
206 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
207 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
208 },
209 "server" => {
210 "port" => [\$server_port, "20081"],
211 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
212 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
213 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
214 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
215 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
216 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
217 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
218 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
219 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
220 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
221 "repo-path" => [\$repo_path, '/srv/www/repository'],
222 "ldap-uri" => [\$ldap_uri, ""],
223 "ldap-base" => [\$ldap_base, ""],
224 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
225 "ldap-admin-password" => [\$ldap_admin_password, ""],
226 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
227 "max-clients" => [\$max_clients, 10],
228 },
229 "GOsaPackages" => {
230 "ip" => [\$gosa_ip, "0.0.0.0"],
231 "port" => [\$gosa_port, "20082"],
232 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
233 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
234 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
235 "key" => [\$GosaPackages_key, "none"],
236 },
237 "ClientPackages" => {
238 "key" => [\$ClientPackages_key, "none"],
239 },
240 "ServerPackages"=> {
241 "address" => [\$foreign_server_string, ""],
242 "domain" => [\$server_domain, ""],
243 "key" => [\$ServerPackages_key, "none"],
244 "key-lifetime" => [\$foreign_servers_register_delay, 120],
245 }
246 );
249 #=== FUNCTION ================================================================
250 # NAME: usage
251 # PARAMETERS: nothing
252 # RETURNS: nothing
253 # DESCRIPTION: print out usage text to STDERR
254 #===============================================================================
255 sub usage {
256 print STDERR << "EOF" ;
257 usage: $prg [-hvf] [-c config]
259 -h : this (help) message
260 -c <file> : config file
261 -f : foreground, process will not be forked to background
262 -v : be verbose (multiple to increase verbosity)
263 -no-arp : starts $prg without connection to arp module
265 EOF
266 print "\n" ;
267 }
270 #=== FUNCTION ================================================================
271 # NAME: read_configfile
272 # PARAMETERS: cfg_file - string -
273 # RETURNS: nothing
274 # DESCRIPTION: read cfg_file and set variables
275 #===============================================================================
276 sub read_configfile {
277 my $cfg;
278 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
279 if( -r $cfg_file ) {
280 $cfg = Config::IniFiles->new( -file => $cfg_file );
281 } else {
282 print STDERR "Couldn't read config file!\n";
283 }
284 } else {
285 $cfg = Config::IniFiles->new() ;
286 }
287 foreach my $section (keys %cfg_defaults) {
288 foreach my $param (keys %{$cfg_defaults{ $section }}) {
289 my $pinfo = $cfg_defaults{ $section }{ $param };
290 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
291 }
292 }
293 }
296 #=== FUNCTION ================================================================
297 # NAME: logging
298 # PARAMETERS: level - string - default 'info'
299 # msg - string -
300 # facility - string - default 'LOG_DAEMON'
301 # RETURNS: nothing
302 # DESCRIPTION: function for logging
303 #===============================================================================
304 sub daemon_log {
305 # log into log_file
306 my( $msg, $level ) = @_;
307 if(not defined $msg) { return }
308 if(not defined $level) { $level = 1 }
309 if(defined $log_file){
310 open(LOG_HANDLE, ">>$log_file");
311 chmod 0600, $log_file;
312 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
313 print STDERR "cannot open $log_file: $!";
314 return
315 }
316 chomp($msg);
317 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
318 if($level <= $verbose){
319 my ($seconds, $minutes, $hours, $monthday, $month,
320 $year, $weekday, $yearday, $sommertime) = localtime(time);
321 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
322 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
323 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
324 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
325 $month = $monthnames[$month];
326 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
327 $year+=1900;
328 my $name = $prg;
330 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
331 print LOG_HANDLE $log_msg;
332 if( $foreground ) {
333 print STDERR $log_msg;
334 }
335 }
336 close( LOG_HANDLE );
337 }
338 }
341 #=== FUNCTION ================================================================
342 # NAME: check_cmdline_param
343 # PARAMETERS: nothing
344 # RETURNS: nothing
345 # DESCRIPTION: validates commandline parameter
346 #===============================================================================
347 sub check_cmdline_param () {
348 my $err_config;
349 my $err_counter = 0;
350 if(not defined($cfg_file)) {
351 $cfg_file = "/etc/gosa-si/server.conf";
352 if(! -r $cfg_file) {
353 $err_config = "please specify a config file";
354 $err_counter += 1;
355 }
356 }
357 if( $err_counter > 0 ) {
358 &usage( "", 1 );
359 if( defined( $err_config)) { print STDERR "$err_config\n"}
360 print STDERR "\n";
361 exit( -1 );
362 }
363 }
366 #=== FUNCTION ================================================================
367 # NAME: check_pid
368 # PARAMETERS: nothing
369 # RETURNS: nothing
370 # DESCRIPTION: handels pid processing
371 #===============================================================================
372 sub check_pid {
373 $pid = -1;
374 # Check, if we are already running
375 if( open(LOCK_FILE, "<$pid_file") ) {
376 $pid = <LOCK_FILE>;
377 if( defined $pid ) {
378 chomp( $pid );
379 if( -f "/proc/$pid/stat" ) {
380 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
381 if( $stat ) {
382 daemon_log("ERROR: Already running",1);
383 close( LOCK_FILE );
384 exit -1;
385 }
386 }
387 }
388 close( LOCK_FILE );
389 unlink( $pid_file );
390 }
392 # create a syslog msg if it is not to possible to open PID file
393 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
394 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
395 if (open(LOCK_FILE, '<', $pid_file)
396 && ($pid = <LOCK_FILE>))
397 {
398 chomp($pid);
399 $msg .= "(PID $pid)\n";
400 } else {
401 $msg .= "(unable to read PID)\n";
402 }
403 if( ! ($foreground) ) {
404 openlog( $0, "cons,pid", "daemon" );
405 syslog( "warning", $msg );
406 closelog();
407 }
408 else {
409 print( STDERR " $msg " );
410 }
411 exit( -1 );
412 }
413 }
415 #=== FUNCTION ================================================================
416 # NAME: import_modules
417 # PARAMETERS: module_path - string - abs. path to the directory the modules
418 # are stored
419 # RETURNS: nothing
420 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
421 # state is on is imported by "require 'file';"
422 #===============================================================================
423 sub import_modules {
424 daemon_log(" ", 1);
426 if (not -e $modules_path) {
427 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
428 }
430 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
431 while (defined (my $file = readdir (DIR))) {
432 if (not $file =~ /(\S*?).pm$/) {
433 next;
434 }
435 my $mod_name = $1;
437 if( $file =~ /ArpHandler.pm/ ) {
438 if( $no_arp > 0 ) {
439 next;
440 }
441 }
443 eval { require $file; };
444 if ($@) {
445 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
446 daemon_log("$@", 5);
447 } else {
448 my $info = eval($mod_name.'::get_module_info()');
449 # Only load module if get_module_info() returns a non-null object
450 if( $info ) {
451 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
452 $known_modules->{$mod_name} = $info;
453 daemon_log("0 INFO: module $mod_name loaded", 5);
454 }
455 }
456 }
457 close (DIR);
458 }
461 #=== FUNCTION ================================================================
462 # NAME: sig_int_handler
463 # PARAMETERS: signal - string - signal arose from system
464 # RETURNS: noting
465 # DESCRIPTION: handels tasks to be done befor signal becomes active
466 #===============================================================================
467 sub sig_int_handler {
468 my ($signal) = @_;
470 # if (defined($ldap_handle)) {
471 # $ldap_handle->disconnect;
472 # }
473 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
476 daemon_log("shutting down gosa-si-server", 1);
477 system("kill `ps -C gosa-si-server -o pid=`");
478 }
479 $SIG{INT} = \&sig_int_handler;
482 sub check_key_and_xml_validity {
483 my ($crypted_msg, $module_key, $session_id) = @_;
484 my $msg;
485 my $msg_hash;
486 my $error_string;
487 eval{
488 $msg = &decrypt_msg($crypted_msg, $module_key);
490 if ($msg =~ /<xml>/i){
491 $msg =~ s/\s+/ /g; # just for better daemon_log
492 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
493 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
495 ##############
496 # check header
497 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
498 my $header_l = $msg_hash->{'header'};
499 if( 1 > @{$header_l} ) { die 'empty header tag'; }
500 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
501 my $header = @{$header_l}[0];
502 if( 0 == length $header) { die 'empty string in header tag'; }
504 ##############
505 # check source
506 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
507 my $source_l = $msg_hash->{'source'};
508 if( 1 > @{$source_l} ) { die 'empty source tag'; }
509 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
510 my $source = @{$source_l}[0];
511 if( 0 == length $source) { die 'source error'; }
513 ##############
514 # check target
515 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
516 my $target_l = $msg_hash->{'target'};
517 if( 1 > @{$target_l} ) { die 'empty target tag'; }
518 }
519 };
520 if($@) {
521 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
522 $msg = undef;
523 $msg_hash = undef;
524 }
526 return ($msg, $msg_hash);
527 }
530 sub check_outgoing_xml_validity {
531 my ($msg) = @_;
533 my $msg_hash;
534 eval{
535 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
537 ##############
538 # check header
539 my $header_l = $msg_hash->{'header'};
540 if( 1 != @{$header_l} ) {
541 die 'no or more than one headers specified';
542 }
543 my $header = @{$header_l}[0];
544 if( 0 == length $header) {
545 die 'header has length 0';
546 }
548 ##############
549 # check source
550 my $source_l = $msg_hash->{'source'};
551 if( 1 != @{$source_l} ) {
552 die 'no or more than 1 sources specified';
553 }
554 my $source = @{$source_l}[0];
555 if( 0 == length $source) {
556 die 'source has length 0';
557 }
558 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
559 $source =~ /^GOSA$/i ) {
560 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
561 }
563 ##############
564 # check target
565 my $target_l = $msg_hash->{'target'};
566 if( 0 == @{$target_l} ) {
567 die "no targets specified";
568 }
569 foreach my $target (@$target_l) {
570 if( 0 == length $target) {
571 die "target has length 0";
572 }
573 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
574 $target =~ /^GOSA$/i ||
575 $target =~ /^\*$/ ||
576 $target =~ /KNOWN_SERVER/i ||
577 $target =~ /JOBDB/i ||
578 $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 ){
579 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
580 }
581 }
582 };
583 if($@) {
584 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
585 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
586 $msg_hash = undef;
587 }
589 return ($msg_hash);
590 }
593 sub input_from_known_server {
594 my ($input, $remote_ip, $session_id) = @_ ;
595 my ($msg, $msg_hash, $module);
597 my $sql_statement= "SELECT * FROM known_server";
598 my $query_res = $known_server_db->select_dbentry( $sql_statement );
600 while( my ($hit_num, $hit) = each %{ $query_res } ) {
601 my $host_name = $hit->{hostname};
602 if( not $host_name =~ "^$remote_ip") {
603 next;
604 }
605 my $host_key = $hit->{hostkey};
606 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
607 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
609 # check if module can open msg envelope with module key
610 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
611 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
612 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
613 daemon_log("$@", 8);
614 next;
615 }
616 else {
617 $msg = $tmp_msg;
618 $msg_hash = $tmp_msg_hash;
619 $module = "ServerPackages";
620 last;
621 }
622 }
624 if( (!$msg) || (!$msg_hash) || (!$module) ) {
625 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
626 }
628 return ($msg, $msg_hash, $module);
629 }
632 sub input_from_known_client {
633 my ($input, $remote_ip, $session_id) = @_ ;
634 my ($msg, $msg_hash, $module);
636 my $sql_statement= "SELECT * FROM known_clients";
637 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
638 while( my ($hit_num, $hit) = each %{ $query_res } ) {
639 my $host_name = $hit->{hostname};
640 if( not $host_name =~ /^$remote_ip:\d*$/) {
641 next;
642 }
643 my $host_key = $hit->{hostkey};
644 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
645 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
647 # check if module can open msg envelope with module key
648 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
650 if( (!$msg) || (!$msg_hash) ) {
651 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
652 &daemon_log("$@", 8);
653 next;
654 }
655 else {
656 $module = "ClientPackages";
657 last;
658 }
659 }
661 if( (!$msg) || (!$msg_hash) || (!$module) ) {
662 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
663 }
665 return ($msg, $msg_hash, $module);
666 }
669 sub input_from_unknown_host {
670 no strict "refs";
671 my ($input, $session_id) = @_ ;
672 my ($msg, $msg_hash, $module);
673 my $error_string;
675 my %act_modules = %$known_modules;
677 while( my ($mod, $info) = each(%act_modules)) {
679 # check a key exists for this module
680 my $module_key = ${$mod."_key"};
681 if( not defined $module_key ) {
682 if( $mod eq 'ArpHandler' ) {
683 next;
684 }
685 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
686 next;
687 }
688 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
690 # check if module can open msg envelope with module key
691 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
692 if( (not defined $msg) || (not defined $msg_hash) ) {
693 next;
694 }
695 else {
696 $module = $mod;
697 last;
698 }
699 }
701 if( (!$msg) || (!$msg_hash) || (!$module)) {
702 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
703 }
705 return ($msg, $msg_hash, $module);
706 }
709 sub create_ciphering {
710 my ($passwd) = @_;
711 if((!defined($passwd)) || length($passwd)==0) {
712 $passwd = "";
713 }
714 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
715 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
716 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
717 $my_cipher->set_iv($iv);
718 return $my_cipher;
719 }
722 sub encrypt_msg {
723 my ($msg, $key) = @_;
724 my $my_cipher = &create_ciphering($key);
725 my $len;
726 {
727 use bytes;
728 $len= 16-length($msg)%16;
729 }
730 $msg = "\0"x($len).$msg;
731 $msg = $my_cipher->encrypt($msg);
732 chomp($msg = &encode_base64($msg));
733 # there are no newlines allowed inside msg
734 $msg=~ s/\n//g;
735 return $msg;
736 }
739 sub decrypt_msg {
741 my ($msg, $key) = @_ ;
742 $msg = &decode_base64($msg);
743 my $my_cipher = &create_ciphering($key);
744 $msg = $my_cipher->decrypt($msg);
745 $msg =~ s/\0*//g;
746 return $msg;
747 }
750 sub get_encrypt_key {
751 my ($target) = @_ ;
752 my $encrypt_key;
753 my $error = 0;
755 # target can be in known_server
756 if( not defined $encrypt_key ) {
757 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
758 my $query_res = $known_server_db->select_dbentry( $sql_statement );
759 while( my ($hit_num, $hit) = each %{ $query_res } ) {
760 my $host_name = $hit->{hostname};
761 if( $host_name ne $target ) {
762 next;
763 }
764 $encrypt_key = $hit->{hostkey};
765 last;
766 }
767 }
769 # target can be in known_client
770 if( not defined $encrypt_key ) {
771 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
772 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
773 while( my ($hit_num, $hit) = each %{ $query_res } ) {
774 my $host_name = $hit->{hostname};
775 if( $host_name ne $target ) {
776 next;
777 }
778 $encrypt_key = $hit->{hostkey};
779 last;
780 }
781 }
783 return $encrypt_key;
784 }
787 #=== FUNCTION ================================================================
788 # NAME: open_socket
789 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
790 # [PeerPort] string necessary if port not appended by PeerAddr
791 # RETURNS: socket IO::Socket::INET
792 # DESCRIPTION: open a socket to PeerAddr
793 #===============================================================================
794 sub open_socket {
795 my ($PeerAddr, $PeerPort) = @_ ;
796 if(defined($PeerPort)){
797 $PeerAddr = $PeerAddr.":".$PeerPort;
798 }
799 my $socket;
800 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
801 Porto => "tcp",
802 Type => SOCK_STREAM,
803 Timeout => 5,
804 );
805 if(not defined $socket) {
806 return;
807 }
808 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
809 return $socket;
810 }
813 # moved to GosaSupportDaemon: 03-06-2008: rettenbe
814 #=== FUNCTION ================================================================
815 # NAME: get_ip
816 # PARAMETERS: interface name (i.e. eth0)
817 # RETURNS: (ip address)
818 # DESCRIPTION: Uses ioctl to get ip address directly from system.
819 #===============================================================================
820 #sub get_ip {
821 # my $ifreq= shift;
822 # my $result= "";
823 # my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
824 # my $proto= getprotobyname('ip');
825 #
826 # socket SOCKET, PF_INET, SOCK_DGRAM, $proto
827 # or die "socket: $!";
828 #
829 # if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
830 # my ($if, $sin) = unpack 'a16 a16', $ifreq;
831 # my ($port, $addr) = sockaddr_in $sin;
832 # my $ip = inet_ntoa $addr;
833 #
834 # if ($ip && length($ip) > 0) {
835 # $result = $ip;
836 # }
837 # }
838 #
839 # return $result;
840 #}
843 sub get_local_ip_for_remote_ip {
844 my $remote_ip= shift;
845 my $result="0.0.0.0";
847 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
848 if($remote_ip eq "127.0.0.1") {
849 $result = "127.0.0.1";
850 } else {
851 my $PROC_NET_ROUTE= ('/proc/net/route');
853 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
854 or die "Could not open $PROC_NET_ROUTE";
856 my @ifs = <PROC_NET_ROUTE>;
858 close(PROC_NET_ROUTE);
860 # Eat header line
861 shift @ifs;
862 chomp @ifs;
863 foreach my $line(@ifs) {
864 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
865 my $destination;
866 my $mask;
867 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
868 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
869 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
870 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
871 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
872 # destination matches route, save mac and exit
873 $result= &get_ip($Iface);
874 last;
875 }
876 }
877 }
878 } else {
879 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
880 }
881 return $result;
882 }
885 sub send_msg_to_target {
886 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
887 my $error = 0;
888 my $header;
889 my $timestamp = &get_time();
890 my $new_status;
891 my $act_status;
892 my ($sql_statement, $res);
894 if( $msg_header ) {
895 $header = "'$msg_header'-";
896 } else {
897 $header = "";
898 }
900 # Patch the source ip
901 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
902 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
903 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
904 }
906 # encrypt xml msg
907 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
909 # opensocket
910 my $socket = &open_socket($address);
911 if( !$socket ) {
912 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
913 $error++;
914 }
916 if( $error == 0 ) {
917 # send xml msg
918 print $socket $crypted_msg."\n";
920 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
921 daemon_log("$session_id DEBUG: message:\n$msg", 9);
923 }
925 # close socket in any case
926 if( $socket ) {
927 close $socket;
928 }
930 if( $error > 0 ) { $new_status = "down"; }
931 else { $new_status = $msg_header; }
934 # known_clients
935 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
936 $res = $known_clients_db->select_dbentry($sql_statement);
937 if( keys(%$res) == 1) {
938 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
939 if ($act_status eq "down" && $new_status eq "down") {
940 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
941 $res = $known_clients_db->del_dbentry($sql_statement);
942 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
943 } else {
944 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
945 $res = $known_clients_db->update_dbentry($sql_statement);
946 if($new_status eq "down"){
947 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
948 } else {
949 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
950 }
951 }
952 }
954 # known_server
955 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
956 $res = $known_server_db->select_dbentry($sql_statement);
957 if( keys(%$res) == 1) {
958 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
959 if ($act_status eq "down" && $new_status eq "down") {
960 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
961 $res = $known_server_db->del_dbentry($sql_statement);
962 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
963 }
964 else {
965 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
966 $res = $known_server_db->update_dbentry($sql_statement);
967 if($new_status eq "down"){
968 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
969 } else {
970 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
971 }
972 }
973 }
974 return $error;
975 }
978 sub update_jobdb_status_for_send_msgs {
979 my ($answer, $error) = @_;
980 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
981 my $jobdb_id = $1;
983 # sending msg faild
984 if( $error ) {
985 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
986 my $sql_statement = "UPDATE $job_queue_tn ".
987 "SET status='error', result='can not deliver msg, please consult log file' ".
988 "WHERE id=$jobdb_id";
989 my $res = $job_db->update_dbentry($sql_statement);
990 }
992 # sending msg was successful
993 } else {
994 my $sql_statement = "UPDATE $job_queue_tn ".
995 "SET status='done' ".
996 "WHERE id=$jobdb_id AND status='processed'";
997 my $res = $job_db->update_dbentry($sql_statement);
998 }
999 }
1000 }
1003 sub sig_handler {
1004 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1005 daemon_log("0 INFO got signal '$signal'", 1);
1006 $kernel->sig_handled();
1007 return;
1008 }
1011 sub msg_to_decrypt {
1012 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1013 my $session_id = $session->ID;
1014 my ($msg, $msg_hash, $module);
1015 my $error = 0;
1017 # hole neue msg aus @msgs_to_decrypt
1018 my $next_msg = shift @msgs_to_decrypt;
1020 # entschlüssle sie
1022 # msg is from a new client or gosa
1023 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1024 # msg is from a gosa-si-server
1025 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1026 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1027 }
1028 # msg is from a gosa-si-client
1029 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1030 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1031 }
1032 # an error occurred
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1035 # could not understand a msg from its server the client cause a re-registering process
1036 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1037 "' to cause a re-registering of the client if necessary", 5);
1038 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1039 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1040 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1041 my $host_name = $hit->{'hostname'};
1042 my $host_key = $hit->{'hostkey'};
1043 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1044 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1045 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1046 }
1047 $error++;
1048 }
1051 my $header;
1052 my $target;
1053 my $source;
1054 my $done = 0;
1055 my $sql;
1056 my $res;
1057 # check whether this message should be processed here
1058 if ($error == 0) {
1059 $header = @{$msg_hash->{'header'}}[0];
1060 $target = @{$msg_hash->{'target'}}[0];
1061 $source = @{$msg_hash->{'source'}}[0];
1062 my ($target_ip, $target_port) = split(':', $target);
1063 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1064 my $server_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1065 }
1067 # target and source is equal to GOSA -> process here
1068 if (not $done) {
1069 if ($target eq "GOSA" && $source eq "GOSA") {
1070 $done = 1;
1071 }
1072 }
1074 # target is own address without forward_to_gosa-tag -> process here
1075 if (not $done) {
1076 if (($target eq $server_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1077 $done = 1;
1078 if ($source eq "GOSA") {
1079 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1080 }
1081 print STDERR "target is own address without forward_to_gosa-tag -> process here\n";
1082 }
1083 }
1085 # target is a client address in known_clients -> process here
1086 if (not $done) {
1087 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1088 $res = $known_clients_db->select_dbentry($sql);
1089 if (keys(%$res) > 0) {
1090 $done = 1;
1091 my $hostname = $res->{1}->{'hostname'};
1092 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1093 print STDERR "target is a client address in known_clients -> process here\n";
1094 }
1095 }
1097 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1098 if (not $done) {
1099 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1100 my $gosa_at;
1101 my $gosa_session_id;
1102 if (($target eq $server_address) && (defined $forward_to_gosa)){
1103 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1104 if ($gosa_at ne $server_address) {
1105 $done = 1;
1106 print STDERR "target is own address with forward_to_gosa-tag not pointing to myself -> process here\n";
1107 }
1108 }
1109 }
1111 # if message should be processed here -> add message to incoming_db
1112 if ($done) {
1114 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1115 # so gosa-si-server knows how to process this kind of messages
1116 if ($header =~ /^gosa_/ || $header =~ /job_/) {
1117 $module = "GosaPackages";
1118 }
1120 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1121 primkey=>[],
1122 headertag=>$header,
1123 targettag=>$target,
1124 xmlmessage=>&encode_base64($msg),
1125 timestamp=>&get_time,
1126 module=>$module,
1127 sessionid=>$session_id,
1128 } );
1130 }
1132 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1133 if (not $done) {
1134 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1135 my $gosa_at;
1136 my $gosa_session_id;
1137 if (($target eq $server_address) && (defined $forward_to_gosa)){
1138 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1139 if ($gosa_at eq $server_address) {
1140 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1141 if( defined $session_reference ) {
1142 $heap = $session_reference->get_heap();
1143 }
1144 if(exists $heap->{'client'}) {
1145 $msg = &encrypt_msg($msg, $GosaPackages_key);
1146 $heap->{'client'}->put($msg);
1147 }
1148 $done = 1;
1149 print STDERR "target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa\n";
1150 }
1151 }
1153 }
1155 # target is a client address in foreign_clients -> forward to registration server
1156 if (not $done) {
1157 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1158 $res = $foreign_clients_db->select_dbentry($sql);
1159 if (keys(%$res) > 0) {
1160 my $hostname = $res->{1}->{'hostname'};
1161 my $regserver = $res->{1}->{'regserver'};
1162 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1163 my $res = $known_server_db->select_dbentry($sql);
1164 if (keys(%$res) > 0) {
1165 my $regserver_key = $res->{1}->{'hostkey'};
1166 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1167 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1168 if ($source eq "GOSA") {
1169 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1170 }
1171 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1172 }
1173 $done = 1;
1174 print STDERR "target is a client address in foreign_clients -> forward to registration server\n";
1175 }
1176 }
1178 # target is a server address -> forward to server
1179 if (not $done) {
1180 $sql = "SELECT * FROM $known_server_tn WHERE hostname='$target'";
1181 $res = $known_server_db->select_dbentry($sql);
1182 if (keys(%$res) > 0) {
1183 my $hostkey = $res->{1}->{'hostkey'};
1185 if ($source eq "GOSA") {
1186 $msg =~ s/<source>GOSA<\/source>/<source>$server_address<\/source>/;
1187 $msg =~ s/<\/xml>/<forward_to_gosa>$server_address,$session_id<\/forward_to_gosa><\/xml>/;
1189 }
1191 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1192 $done = 1;
1193 print STDERR "target is a server address -> forward to server\n";
1194 }
1197 }
1199 if (not $done) {
1200 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1201 }
1202 }
1204 return;
1205 }
1208 sub next_task {
1209 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1210 my $running_task = POE::Wheel::Run->new(
1211 Program => sub { process_task($session, $heap, $task) },
1212 StdioFilter => POE::Filter::Reference->new(),
1213 StdoutEvent => "task_result",
1214 StderrEvent => "task_debug",
1215 CloseEvent => "task_done",
1216 );
1217 $heap->{task}->{ $running_task->ID } = $running_task;
1218 }
1220 sub handle_task_result {
1221 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1222 my $client_answer = $result->{'answer'};
1223 if( $client_answer =~ s/session_id=(\d+)$// ) {
1224 my $session_id = $1;
1225 if( defined $session_id ) {
1226 my $session_reference = $kernel->ID_id_to_session($session_id);
1227 if( defined $session_reference ) {
1228 $heap = $session_reference->get_heap();
1229 }
1230 }
1232 if(exists $heap->{'client'}) {
1233 $heap->{'client'}->put($client_answer);
1234 }
1235 }
1236 $kernel->sig(CHLD => "child_reap");
1237 }
1239 sub handle_task_debug {
1240 my $result = $_[ARG0];
1241 print STDERR "$result\n";
1242 }
1244 sub handle_task_done {
1245 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1246 delete $heap->{task}->{$task_id};
1247 }
1249 sub process_task {
1250 no strict "refs";
1251 my ($session, $heap, $task) = @_;
1252 my $error = 0;
1253 my $answer_l;
1254 my ($answer_header, @answer_target_l, $answer_source);
1255 my $client_answer = "";
1257 # prepare all variables needed to process message
1258 #my $msg = $task->{'xmlmessage'};
1259 my $msg = &decode_base64($task->{'xmlmessage'});
1260 my $incoming_id = $task->{'id'};
1261 my $module = $task->{'module'};
1262 my $header = $task->{'headertag'};
1263 my $session_id = $task->{'sessionid'};
1264 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1265 my $source = @{$msg_hash->{'source'}}[0];
1267 # set timestamp of incoming client uptodate, so client will not
1268 # be deleted from known_clients because of expiration
1269 my $act_time = &get_time();
1270 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1271 my $res = $known_clients_db->exec_statement($sql);
1273 ######################
1274 # process incoming msg
1275 if( $error == 0) {
1276 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1277 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1278 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1280 if ( 0 < @{$answer_l} ) {
1281 my $answer_str = join("\n", @{$answer_l});
1282 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1283 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1284 }
1285 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1286 } else {
1287 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1288 }
1290 }
1291 if( !$answer_l ) { $error++ };
1293 ########
1294 # answer
1295 if( $error == 0 ) {
1297 foreach my $answer ( @{$answer_l} ) {
1298 # check outgoing msg to xml validity
1299 my $answer_hash = &check_outgoing_xml_validity($answer);
1300 if( not defined $answer_hash ) { next; }
1302 $answer_header = @{$answer_hash->{'header'}}[0];
1303 @answer_target_l = @{$answer_hash->{'target'}};
1304 $answer_source = @{$answer_hash->{'source'}}[0];
1306 # deliver msg to all targets
1307 foreach my $answer_target ( @answer_target_l ) {
1309 # targets of msg are all gosa-si-clients in known_clients_db
1310 if( $answer_target eq "*" ) {
1311 # answer is for all clients
1312 my $sql_statement= "SELECT * FROM known_clients";
1313 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1314 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1315 my $host_name = $hit->{hostname};
1316 my $host_key = $hit->{hostkey};
1317 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1318 &update_jobdb_status_for_send_msgs($answer, $error);
1319 }
1320 }
1322 # targets of msg are all gosa-si-server in known_server_db
1323 elsif( $answer_target eq "KNOWN_SERVER" ) {
1324 # answer is for all server in known_server
1325 my $sql_statement= "SELECT * FROM $known_server_tn";
1326 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1327 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1328 my $host_name = $hit->{hostname};
1329 my $host_key = $hit->{hostkey};
1330 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1331 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1332 &update_jobdb_status_for_send_msgs($answer, $error);
1333 }
1334 }
1336 # target of msg is GOsa
1337 elsif( $answer_target eq "GOSA" ) {
1338 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1339 my $add_on = "";
1340 if( defined $session_id ) {
1341 $add_on = ".session_id=$session_id";
1342 }
1343 # answer is for GOSA and has to returned to connected client
1344 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1345 $client_answer = $gosa_answer.$add_on;
1346 }
1348 # target of msg is job queue at this host
1349 elsif( $answer_target eq "JOBDB") {
1350 $answer =~ /<header>(\S+)<\/header>/;
1351 my $header;
1352 if( defined $1 ) { $header = $1; }
1353 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1354 &update_jobdb_status_for_send_msgs($answer, $error);
1355 }
1357 # target of msg is a mac address
1358 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 ) {
1359 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1360 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1361 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1362 my $found_ip_flag = 0;
1363 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1364 my $host_name = $hit->{hostname};
1365 my $host_key = $hit->{hostkey};
1366 $answer =~ s/$answer_target/$host_name/g;
1367 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1368 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1369 &update_jobdb_status_for_send_msgs($answer, $error);
1370 $found_ip_flag++ ;
1371 }
1372 if( $found_ip_flag == 0) {
1373 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1374 }
1376 # answer is for one specific host
1377 } else {
1378 # get encrypt_key
1379 my $encrypt_key = &get_encrypt_key($answer_target);
1380 if( not defined $encrypt_key ) {
1381 # unknown target
1382 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1383 next;
1384 }
1385 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1386 &update_jobdb_status_for_send_msgs($answer, $error);
1387 }
1388 }
1389 }
1390 }
1392 my $filter = POE::Filter::Reference->new();
1393 my %result = (
1394 status => "seems ok to me",
1395 answer => $client_answer,
1396 );
1398 my $output = $filter->put( [ \%result ] );
1399 print @$output;
1402 }
1404 sub session_start {
1405 my ($kernel) = $_[KERNEL];
1406 &trigger_db_loop($kernel);
1407 $global_kernel = $kernel;
1408 $kernel->yield('register_at_foreign_servers');
1409 $kernel->yield('create_fai_server_db', $fai_server_tn );
1410 $kernel->yield('create_fai_release_db', $fai_release_tn );
1411 $kernel->yield('watch_for_next_tasks');
1412 $kernel->sig(USR1 => "sig_handler");
1413 $kernel->sig(USR2 => "create_packages_list_db");
1414 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1415 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1416 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1417 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1418 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1419 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1421 }
1423 sub trigger_db_loop {
1424 my ($kernel) = @_ ;
1425 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1426 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1427 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1428 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1429 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1430 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1431 }
1434 sub watch_for_done_jobs {
1435 my ($kernel,$heap) = @_[KERNEL, HEAP];
1437 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1438 my $res = $job_db->select_dbentry( $sql_statement );
1440 while( my ($id, $hit) = each %{$res} ) {
1441 my $jobdb_id = $hit->{id};
1442 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1443 my $res = $job_db->del_dbentry($sql_statement);
1444 }
1446 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1447 }
1450 sub watch_for_new_jobs {
1451 if($watch_for_new_jobs_in_progress == 0) {
1452 $watch_for_new_jobs_in_progress = 1;
1453 my ($kernel,$heap) = @_[KERNEL, HEAP];
1455 # check gosa job queue for jobs with executable timestamp
1456 my $timestamp = &get_time();
1457 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1458 my $res = $job_db->exec_statement( $sql_statement );
1460 # Merge all new jobs that would do the same actions
1461 my @drops;
1462 my $hits;
1463 foreach my $hit (reverse @{$res} ) {
1464 my $macaddress= lc @{$hit}[8];
1465 my $headertag= @{$hit}[5];
1466 if(
1467 defined($hits->{$macaddress}) &&
1468 defined($hits->{$macaddress}->{$headertag}) &&
1469 defined($hits->{$macaddress}->{$headertag}[0])
1470 ) {
1471 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1472 }
1473 $hits->{$macaddress}->{$headertag}= $hit;
1474 }
1476 # Delete new jobs with a matching job in state 'processing'
1477 foreach my $macaddress (keys %{$hits}) {
1478 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1479 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1480 if(defined($jobdb_id)) {
1481 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1482 my $res = $job_db->exec_statement( $sql_statement );
1483 foreach my $hit (@{$res}) {
1484 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1485 }
1486 } else {
1487 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1488 }
1489 }
1490 }
1492 # Commit deletion
1493 $job_db->exec_statementlist(\@drops);
1495 # Look for new jobs that could be executed
1496 foreach my $macaddress (keys %{$hits}) {
1498 # Look if there is an executing job
1499 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1500 my $res = $job_db->exec_statement( $sql_statement );
1502 # Skip new jobs for host if there is a processing job
1503 if(defined($res) and defined @{$res}[0]) {
1504 next;
1505 }
1507 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1508 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1509 if(defined($jobdb_id)) {
1510 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1512 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1513 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1514 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1516 # expect macaddress is unique!!!!!!
1517 my $target = $res_hash->{1}->{hostname};
1519 # change header
1520 $job_msg =~ s/<header>job_/<header>gosa_/;
1522 # add sqlite_id
1523 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1525 $job_msg =~ /<header>(\S+)<\/header>/;
1526 my $header = $1 ;
1527 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1529 # update status in job queue to 'processing'
1530 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1531 my $res = $job_db->update_dbentry($sql_statement);
1532 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1534 # We don't want parallel processing
1535 last;
1536 }
1537 }
1538 }
1540 $watch_for_new_jobs_in_progress = 0;
1541 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1542 }
1543 }
1546 sub watch_for_new_messages {
1547 my ($kernel,$heap) = @_[KERNEL, HEAP];
1548 my @coll_user_msg; # collection list of outgoing messages
1550 # check messaging_db for new incoming messages with executable timestamp
1551 my $timestamp = &get_time();
1552 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1553 my $res = $messaging_db->exec_statement( $sql_statement );
1554 foreach my $hit (@{$res}) {
1556 # create outgoing messages
1557 my $message_to = @{$hit}[3];
1558 # translate message_to to plain login name
1559 my @message_to_l = split(/,/, $message_to);
1560 my %receiver_h;
1561 foreach my $receiver (@message_to_l) {
1562 if ($receiver =~ /^u_([\s\S]*)$/) {
1563 $receiver_h{$1} = 0;
1564 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1565 my $group_name = $1;
1566 # fetch all group members from ldap and add them to receiver hash
1567 my $ldap_handle = &get_ldap_handle();
1568 if (defined $ldap_handle) {
1569 my $mesg = $ldap_handle->search(
1570 base => $ldap_base,
1571 scope => 'sub',
1572 attrs => ['memberUid'],
1573 filter => "cn=$group_name",
1574 );
1575 if ($mesg->count) {
1576 my @entries = $mesg->entries;
1577 foreach my $entry (@entries) {
1578 my @receivers= $entry->get_value("memberUid");
1579 foreach my $receiver (@receivers) {
1580 $receiver_h{$1} = 0;
1581 }
1582 }
1583 }
1584 # translating errors ?
1585 if ($mesg->code) {
1586 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1587 }
1588 # ldap handle error ?
1589 } else {
1590 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1591 }
1592 } else {
1593 my $sbjct = &encode_base64(@{$hit}[1]);
1594 my $msg = &encode_base64(@{$hit}[7]);
1595 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1596 }
1597 }
1598 my @receiver_l = keys(%receiver_h);
1600 my $message_id = @{$hit}[0];
1602 #add each outgoing msg to messaging_db
1603 my $receiver;
1604 foreach $receiver (@receiver_l) {
1605 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1606 "VALUES ('".
1607 $message_id."', '". # id
1608 @{$hit}[1]."', '". # subject
1609 @{$hit}[2]."', '". # message_from
1610 $receiver."', '". # message_to
1611 "none"."', '". # flag
1612 "out"."', '". # direction
1613 @{$hit}[6]."', '". # delivery_time
1614 @{$hit}[7]."', '". # message
1615 $timestamp."'". # timestamp
1616 ")";
1617 &daemon_log("M DEBUG: $sql_statement", 1);
1618 my $res = $messaging_db->exec_statement($sql_statement);
1619 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1620 }
1622 # set incoming message to flag d=deliverd
1623 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1624 &daemon_log("M DEBUG: $sql_statement", 7);
1625 $res = $messaging_db->update_dbentry($sql_statement);
1626 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1627 }
1629 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1630 return;
1631 }
1633 sub watch_for_delivery_messages {
1634 my ($kernel, $heap) = @_[KERNEL, HEAP];
1636 # select outgoing messages
1637 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1638 #&daemon_log("0 DEBUG: $sql", 7);
1639 my $res = $messaging_db->exec_statement( $sql_statement );
1641 # build out msg for each usr
1642 foreach my $hit (@{$res}) {
1643 my $receiver = @{$hit}[3];
1644 my $msg_id = @{$hit}[0];
1645 my $subject = @{$hit}[1];
1646 my $message = @{$hit}[7];
1648 # resolve usr -> host where usr is logged in
1649 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1650 #&daemon_log("0 DEBUG: $sql", 7);
1651 my $res = $login_users_db->exec_statement($sql);
1653 # reciver is logged in nowhere
1654 if (not ref(@$res[0]) eq "ARRAY") { next; }
1656 my $send_succeed = 0;
1657 foreach my $hit (@$res) {
1658 my $receiver_host = @$hit[0];
1659 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1661 # fetch key to encrypt msg propperly for usr/host
1662 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1663 &daemon_log("0 DEBUG: $sql", 7);
1664 my $res = $known_clients_db->exec_statement($sql);
1666 # host is already down
1667 if (not ref(@$res[0]) eq "ARRAY") { next; }
1669 # host is on
1670 my $receiver_key = @{@{$res}[0]}[2];
1671 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1672 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1673 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1674 if ($error == 0 ) {
1675 $send_succeed++ ;
1676 }
1677 }
1679 if ($send_succeed) {
1680 # set outgoing msg at db to deliverd
1681 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1682 &daemon_log("0 DEBUG: $sql", 7);
1683 my $res = $messaging_db->exec_statement($sql);
1684 }
1685 }
1687 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1688 return;
1689 }
1692 sub watch_for_done_messages {
1693 my ($kernel,$heap) = @_[KERNEL, HEAP];
1695 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1696 #&daemon_log("0 DEBUG: $sql", 7);
1697 my $res = $messaging_db->exec_statement($sql);
1699 foreach my $hit (@{$res}) {
1700 my $msg_id = @{$hit}[0];
1702 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1703 #&daemon_log("0 DEBUG: $sql", 7);
1704 my $res = $messaging_db->exec_statement($sql);
1706 # not all usr msgs have been seen till now
1707 if ( ref(@$res[0]) eq "ARRAY") { next; }
1709 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1710 #&daemon_log("0 DEBUG: $sql", 7);
1711 $res = $messaging_db->exec_statement($sql);
1713 }
1715 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1716 return;
1717 }
1720 sub watch_for_old_known_clients {
1721 my ($kernel,$heap) = @_[KERNEL, HEAP];
1723 my $sql_statement = "SELECT * FROM $known_clients_tn";
1724 my $res = $known_clients_db->select_dbentry( $sql_statement );
1726 my $act_time = int(&get_time());
1728 while ( my ($hit_num, $hit) = each %$res) {
1729 my $expired_timestamp = int($hit->{'timestamp'});
1730 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1731 my $dt = DateTime->new( year => $1,
1732 month => $2,
1733 day => $3,
1734 hour => $4,
1735 minute => $5,
1736 second => $6,
1737 );
1739 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1740 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1741 if ($act_time > $expired_timestamp) {
1742 my $hostname = $hit->{'hostname'};
1743 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1744 my $del_res = $known_clients_db->exec_statement($del_sql);
1746 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1747 }
1749 }
1751 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1752 }
1755 sub watch_for_next_tasks {
1756 my ($kernel,$heap) = @_[KERNEL, HEAP];
1758 my $sql = "SELECT * FROM $incoming_tn";
1759 my $res = $incoming_db->select_dbentry($sql);
1761 while ( my ($hit_num, $hit) = each %$res) {
1762 my $headertag = $hit->{'headertag'};
1763 if ($headertag =~ /^answer_(\d+)/) {
1764 # do not start processing, this message is for a still running POE::Wheel
1765 next;
1766 }
1767 my $message_id = $hit->{'id'};
1768 $kernel->yield('next_task', $hit);
1770 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1771 my $res = $incoming_db->exec_statement($sql);
1772 }
1774 $kernel->delay_set('watch_for_next_tasks', 1);
1775 }
1778 sub get_ldap_handle {
1779 my ($session_id) = @_;
1780 my $heap;
1781 my $ldap_handle;
1783 if (not defined $session_id ) { $session_id = 0 };
1784 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1786 if ($session_id == 0) {
1787 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1788 $ldap_handle = Net::LDAP->new( $ldap_uri );
1789 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1791 } else {
1792 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1793 if( defined $session_reference ) {
1794 $heap = $session_reference->get_heap();
1795 }
1797 if (not defined $heap) {
1798 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1799 return;
1800 }
1802 # TODO: This "if" is nonsense, because it doesn't prove that the
1803 # used handle is still valid - or if we've to reconnect...
1804 #if (not exists $heap->{ldap_handle}) {
1805 $ldap_handle = Net::LDAP->new( $ldap_uri );
1806 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1807 $heap->{ldap_handle} = $ldap_handle;
1808 #}
1809 }
1810 return $ldap_handle;
1811 }
1814 sub change_fai_state {
1815 my ($st, $targets, $session_id) = @_;
1816 $session_id = 0 if not defined $session_id;
1817 # Set FAI state to localboot
1818 my %mapActions= (
1819 reboot => '',
1820 update => 'softupdate',
1821 localboot => 'localboot',
1822 reinstall => 'install',
1823 rescan => '',
1824 wake => '',
1825 memcheck => 'memcheck',
1826 sysinfo => 'sysinfo',
1827 install => 'install',
1828 );
1830 # Return if this is unknown
1831 if (!exists $mapActions{ $st }){
1832 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1833 return;
1834 }
1836 my $state= $mapActions{ $st };
1838 my $ldap_handle = &get_ldap_handle($session_id);
1839 if( defined($ldap_handle) ) {
1841 # Build search filter for hosts
1842 my $search= "(&(objectClass=GOhard)";
1843 foreach (@{$targets}){
1844 $search.= "(macAddress=$_)";
1845 }
1846 $search.= ")";
1848 # If there's any host inside of the search string, procress them
1849 if (!($search =~ /macAddress/)){
1850 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1851 return;
1852 }
1854 # Perform search for Unit Tag
1855 my $mesg = $ldap_handle->search(
1856 base => $ldap_base,
1857 scope => 'sub',
1858 attrs => ['dn', 'FAIstate', 'objectClass'],
1859 filter => "$search"
1860 );
1862 if ($mesg->count) {
1863 my @entries = $mesg->entries;
1864 if (0 == @entries) {
1865 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
1866 }
1868 foreach my $entry (@entries) {
1869 # Only modify entry if it is not set to '$state'
1870 if ($entry->get_value("FAIstate") ne "$state"){
1871 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1872 my $result;
1873 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1874 if (exists $tmp{'FAIobject'}){
1875 if ($state eq ''){
1876 $result= $ldap_handle->modify($entry->dn, changes => [
1877 delete => [ FAIstate => [] ] ]);
1878 } else {
1879 $result= $ldap_handle->modify($entry->dn, changes => [
1880 replace => [ FAIstate => $state ] ]);
1881 }
1882 } elsif ($state ne ''){
1883 $result= $ldap_handle->modify($entry->dn, changes => [
1884 add => [ objectClass => 'FAIobject' ],
1885 add => [ FAIstate => $state ] ]);
1886 }
1888 # Errors?
1889 if ($result->code){
1890 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1891 }
1892 } else {
1893 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1894 }
1895 }
1896 } else {
1897 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1898 }
1900 # if no ldap handle defined
1901 } else {
1902 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1903 }
1905 return;
1906 }
1909 sub change_goto_state {
1910 my ($st, $targets, $session_id) = @_;
1911 $session_id = 0 if not defined $session_id;
1913 # Switch on or off?
1914 my $state= $st eq 'active' ? 'active': 'locked';
1916 my $ldap_handle = &get_ldap_handle($session_id);
1917 if( defined($ldap_handle) ) {
1919 # Build search filter for hosts
1920 my $search= "(&(objectClass=GOhard)";
1921 foreach (@{$targets}){
1922 $search.= "(macAddress=$_)";
1923 }
1924 $search.= ")";
1926 # If there's any host inside of the search string, procress them
1927 if (!($search =~ /macAddress/)){
1928 return;
1929 }
1931 # Perform search for Unit Tag
1932 my $mesg = $ldap_handle->search(
1933 base => $ldap_base,
1934 scope => 'sub',
1935 attrs => ['dn', 'gotoMode'],
1936 filter => "$search"
1937 );
1939 if ($mesg->count) {
1940 my @entries = $mesg->entries;
1941 foreach my $entry (@entries) {
1943 # Only modify entry if it is not set to '$state'
1944 if ($entry->get_value("gotoMode") ne $state){
1946 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1947 my $result;
1948 $result= $ldap_handle->modify($entry->dn, changes => [
1949 replace => [ gotoMode => $state ] ]);
1951 # Errors?
1952 if ($result->code){
1953 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1954 }
1956 }
1957 }
1958 } else {
1959 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
1960 }
1962 }
1963 }
1966 sub run_create_fai_server_db {
1967 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1968 my $session_id = $session->ID;
1969 my $task = POE::Wheel::Run->new(
1970 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1971 StdoutEvent => "session_run_result",
1972 StderrEvent => "session_run_debug",
1973 CloseEvent => "session_run_done",
1974 );
1976 $heap->{task}->{ $task->ID } = $task;
1977 return;
1978 }
1981 sub create_fai_server_db {
1982 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1983 my $result;
1985 if (not defined $session_id) { $session_id = 0; }
1986 my $ldap_handle = &get_ldap_handle();
1987 if(defined($ldap_handle)) {
1988 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1989 my $mesg= $ldap_handle->search(
1990 base => $ldap_base,
1991 scope => 'sub',
1992 attrs => ['FAIrepository', 'gosaUnitTag'],
1993 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1994 );
1995 if($mesg->{'resultCode'} == 0 &&
1996 $mesg->count != 0) {
1997 foreach my $entry (@{$mesg->{entries}}) {
1998 if($entry->exists('FAIrepository')) {
1999 # Add an entry for each Repository configured for server
2000 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2001 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2002 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2003 $result= $fai_server_db->add_dbentry( {
2004 table => $table_name,
2005 primkey => ['server', 'release', 'tag'],
2006 server => $tmp_url,
2007 release => $tmp_release,
2008 sections => $tmp_sections,
2009 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2010 } );
2011 }
2012 }
2013 }
2014 }
2015 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2017 # TODO: Find a way to post the 'create_packages_list_db' event
2018 if(not defined($dont_create_packages_list)) {
2019 &create_packages_list_db(undef, undef, $session_id);
2020 }
2021 }
2023 $ldap_handle->disconnect;
2024 return $result;
2025 }
2028 sub run_create_fai_release_db {
2029 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2030 my $session_id = $session->ID;
2031 my $task = POE::Wheel::Run->new(
2032 Program => sub { &create_fai_release_db($table_name, $session_id) },
2033 StdoutEvent => "session_run_result",
2034 StderrEvent => "session_run_debug",
2035 CloseEvent => "session_run_done",
2036 );
2038 $heap->{task}->{ $task->ID } = $task;
2039 return;
2040 }
2043 sub create_fai_release_db {
2044 my ($table_name, $session_id) = @_;
2045 my $result;
2047 # used for logging
2048 if (not defined $session_id) { $session_id = 0; }
2050 my $ldap_handle = &get_ldap_handle();
2051 if(defined($ldap_handle)) {
2052 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2053 my $mesg= $ldap_handle->search(
2054 base => $ldap_base,
2055 scope => 'sub',
2056 attrs => [],
2057 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2058 );
2059 if($mesg->{'resultCode'} == 0 &&
2060 $mesg->count != 0) {
2061 # Walk through all possible FAI container ou's
2062 my @sql_list;
2063 my $timestamp= &get_time();
2064 foreach my $ou (@{$mesg->{entries}}) {
2065 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2066 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2067 my @tmp_array=get_fai_release_entries($tmp_classes);
2068 if(@tmp_array) {
2069 foreach my $entry (@tmp_array) {
2070 if(defined($entry) && ref($entry) eq 'HASH') {
2071 my $sql=
2072 "INSERT INTO $table_name "
2073 ."(timestamp, release, class, type, state) VALUES ("
2074 .$timestamp.","
2075 ."'".$entry->{'release'}."',"
2076 ."'".$entry->{'class'}."',"
2077 ."'".$entry->{'type'}."',"
2078 ."'".$entry->{'state'}."')";
2079 push @sql_list, $sql;
2080 }
2081 }
2082 }
2083 }
2084 }
2086 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2087 if(@sql_list) {
2088 unshift @sql_list, "VACUUM";
2089 unshift @sql_list, "DELETE FROM $table_name";
2090 $fai_release_db->exec_statementlist(\@sql_list);
2091 }
2092 daemon_log("$session_id DEBUG: Done with inserting",7);
2093 }
2094 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2095 }
2096 $ldap_handle->disconnect;
2097 return $result;
2098 }
2100 sub get_fai_types {
2101 my $tmp_classes = shift || return undef;
2102 my @result;
2104 foreach my $type(keys %{$tmp_classes}) {
2105 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2106 my $entry = {
2107 type => $type,
2108 state => $tmp_classes->{$type}[0],
2109 };
2110 push @result, $entry;
2111 }
2112 }
2114 return @result;
2115 }
2117 sub get_fai_state {
2118 my $result = "";
2119 my $tmp_classes = shift || return $result;
2121 foreach my $type(keys %{$tmp_classes}) {
2122 if(defined($tmp_classes->{$type}[0])) {
2123 $result = $tmp_classes->{$type}[0];
2125 # State is equal for all types in class
2126 last;
2127 }
2128 }
2130 return $result;
2131 }
2133 sub resolve_fai_classes {
2134 my ($fai_base, $ldap_handle, $session_id) = @_;
2135 if (not defined $session_id) { $session_id = 0; }
2136 my $result;
2137 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2138 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2139 my $fai_classes;
2141 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2142 my $mesg= $ldap_handle->search(
2143 base => $fai_base,
2144 scope => 'sub',
2145 attrs => ['cn','objectClass','FAIstate'],
2146 filter => $fai_filter,
2147 );
2148 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2150 if($mesg->{'resultCode'} == 0 &&
2151 $mesg->count != 0) {
2152 foreach my $entry (@{$mesg->{entries}}) {
2153 if($entry->exists('cn')) {
2154 my $tmp_dn= $entry->dn();
2156 # Skip classname and ou dn parts for class
2157 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2159 # Skip classes without releases
2160 if((!defined($tmp_release)) || length($tmp_release)==0) {
2161 next;
2162 }
2164 my $tmp_cn= $entry->get_value('cn');
2165 my $tmp_state= $entry->get_value('FAIstate');
2167 my $tmp_type;
2168 # Get FAI type
2169 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2170 if(grep $_ eq $oclass, @possible_fai_classes) {
2171 $tmp_type= $oclass;
2172 last;
2173 }
2174 }
2176 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2177 # A Subrelease
2178 my @sub_releases = split(/,/, $tmp_release);
2180 # Walk through subreleases and build hash tree
2181 my $hash;
2182 while(my $tmp_sub_release = pop @sub_releases) {
2183 $hash .= "\{'$tmp_sub_release'\}->";
2184 }
2185 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2186 } else {
2187 # A branch, no subrelease
2188 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2189 }
2190 } elsif (!$entry->exists('cn')) {
2191 my $tmp_dn= $entry->dn();
2192 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2194 # Skip classes without releases
2195 if((!defined($tmp_release)) || length($tmp_release)==0) {
2196 next;
2197 }
2199 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2200 # A Subrelease
2201 my @sub_releases= split(/,/, $tmp_release);
2203 # Walk through subreleases and build hash tree
2204 my $hash;
2205 while(my $tmp_sub_release = pop @sub_releases) {
2206 $hash .= "\{'$tmp_sub_release'\}->";
2207 }
2208 # Remove the last two characters
2209 chop($hash);
2210 chop($hash);
2212 eval('$fai_classes->'.$hash.'= {}');
2213 } else {
2214 # A branch, no subrelease
2215 if(!exists($fai_classes->{$tmp_release})) {
2216 $fai_classes->{$tmp_release} = {};
2217 }
2218 }
2219 }
2220 }
2222 # The hash is complete, now we can honor the copy-on-write based missing entries
2223 foreach my $release (keys %$fai_classes) {
2224 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2225 }
2226 }
2227 return $result;
2228 }
2230 sub apply_fai_inheritance {
2231 my $fai_classes = shift || return {};
2232 my $tmp_classes;
2234 # Get the classes from the branch
2235 foreach my $class (keys %{$fai_classes}) {
2236 # Skip subreleases
2237 if($class =~ /^ou=.*$/) {
2238 next;
2239 } else {
2240 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2241 }
2242 }
2244 # Apply to each subrelease
2245 foreach my $subrelease (keys %{$fai_classes}) {
2246 if($subrelease =~ /ou=/) {
2247 foreach my $tmp_class (keys %{$tmp_classes}) {
2248 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2249 $fai_classes->{$subrelease}->{$tmp_class} =
2250 deep_copy($tmp_classes->{$tmp_class});
2251 } else {
2252 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2253 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2254 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2255 deep_copy($tmp_classes->{$tmp_class}->{$type});
2256 }
2257 }
2258 }
2259 }
2260 }
2261 }
2263 # Find subreleases in deeper levels
2264 foreach my $subrelease (keys %{$fai_classes}) {
2265 if($subrelease =~ /ou=/) {
2266 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2267 if($subsubrelease =~ /ou=/) {
2268 apply_fai_inheritance($fai_classes->{$subrelease});
2269 }
2270 }
2271 }
2272 }
2274 return $fai_classes;
2275 }
2277 sub get_fai_release_entries {
2278 my $tmp_classes = shift || return;
2279 my $parent = shift || "";
2280 my @result = shift || ();
2282 foreach my $entry (keys %{$tmp_classes}) {
2283 if(defined($entry)) {
2284 if($entry =~ /^ou=.*$/) {
2285 my $release_name = $entry;
2286 $release_name =~ s/ou=//g;
2287 if(length($parent)>0) {
2288 $release_name = $parent."/".$release_name;
2289 }
2290 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2291 foreach my $bufentry(@bufentries) {
2292 push @result, $bufentry;
2293 }
2294 } else {
2295 my @types = get_fai_types($tmp_classes->{$entry});
2296 foreach my $type (@types) {
2297 push @result,
2298 {
2299 'class' => $entry,
2300 'type' => $type->{'type'},
2301 'release' => $parent,
2302 'state' => $type->{'state'},
2303 };
2304 }
2305 }
2306 }
2307 }
2309 return @result;
2310 }
2312 sub deep_copy {
2313 my $this = shift;
2314 if (not ref $this) {
2315 $this;
2316 } elsif (ref $this eq "ARRAY") {
2317 [map deep_copy($_), @$this];
2318 } elsif (ref $this eq "HASH") {
2319 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2320 } else { die "what type is $_?" }
2321 }
2324 sub session_run_result {
2325 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2326 $kernel->sig(CHLD => "child_reap");
2327 }
2329 sub session_run_debug {
2330 my $result = $_[ARG0];
2331 print STDERR "$result\n";
2332 }
2334 sub session_run_done {
2335 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2336 delete $heap->{task}->{$task_id};
2337 }
2340 sub create_sources_list {
2341 my $session_id = shift;
2342 my $ldap_handle = &main::get_ldap_handle;
2343 my $result="/tmp/gosa_si_tmp_sources_list";
2345 # Remove old file
2346 if(stat($result)) {
2347 unlink($result);
2348 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2349 }
2351 my $fh;
2352 open($fh, ">$result");
2353 if (not defined $fh) {
2354 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2355 return undef;
2356 }
2357 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2358 my $mesg=$ldap_handle->search(
2359 base => $main::ldap_server_dn,
2360 scope => 'base',
2361 attrs => 'FAIrepository',
2362 filter => 'objectClass=FAIrepositoryServer'
2363 );
2364 if($mesg->count) {
2365 foreach my $entry(@{$mesg->{'entries'}}) {
2366 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2367 my ($server, $tag, $release, $sections)= split /\|/, $value;
2368 my $line = "deb $server $release";
2369 $sections =~ s/,/ /g;
2370 $line.= " $sections";
2371 print $fh $line."\n";
2372 }
2373 }
2374 }
2375 } else {
2376 if (defined $main::ldap_server_dn){
2377 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2378 } else {
2379 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2380 }
2381 }
2382 close($fh);
2384 return $result;
2385 }
2388 sub run_create_packages_list_db {
2389 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2390 my $session_id = $session->ID;
2392 my $task = POE::Wheel::Run->new(
2393 Priority => +20,
2394 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2395 StdoutEvent => "session_run_result",
2396 StderrEvent => "session_run_debug",
2397 CloseEvent => "session_run_done",
2398 );
2399 $heap->{task}->{ $task->ID } = $task;
2400 }
2403 sub create_packages_list_db {
2404 my ($ldap_handle, $sources_file, $session_id) = @_;
2406 # it should not be possible to trigger a recreation of packages_list_db
2407 # while packages_list_db is under construction, so set flag packages_list_under_construction
2408 # which is tested befor recreation can be started
2409 if (-r $packages_list_under_construction) {
2410 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2411 return;
2412 } else {
2413 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2414 # set packages_list_under_construction to true
2415 system("touch $packages_list_under_construction");
2416 @packages_list_statements=();
2417 }
2419 if (not defined $session_id) { $session_id = 0; }
2420 if (not defined $ldap_handle) {
2421 $ldap_handle= &get_ldap_handle();
2423 if (not defined $ldap_handle) {
2424 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2425 unlink($packages_list_under_construction);
2426 return;
2427 }
2428 }
2429 if (not defined $sources_file) {
2430 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2431 $sources_file = &create_sources_list($session_id);
2432 }
2434 if (not defined $sources_file) {
2435 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2436 unlink($packages_list_under_construction);
2437 return;
2438 }
2440 my $line;
2442 open(CONFIG, "<$sources_file") or do {
2443 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2444 unlink($packages_list_under_construction);
2445 return;
2446 };
2448 # Read lines
2449 while ($line = <CONFIG>){
2450 # Unify
2451 chop($line);
2452 $line =~ s/^\s+//;
2453 $line =~ s/^\s+/ /;
2455 # Strip comments
2456 $line =~ s/#.*$//g;
2458 # Skip empty lines
2459 if ($line =~ /^\s*$/){
2460 next;
2461 }
2463 # Interpret deb line
2464 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2465 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2466 my $section;
2467 foreach $section (split(' ', $sections)){
2468 &parse_package_info( $baseurl, $dist, $section, $session_id );
2469 }
2470 }
2471 }
2473 close (CONFIG);
2475 find(\&cleanup_and_extract, keys( %repo_dirs ));
2476 &main::strip_packages_list_statements();
2477 unshift @packages_list_statements, "VACUUM";
2478 $packages_list_db->exec_statementlist(\@packages_list_statements);
2479 unlink($packages_list_under_construction);
2480 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2481 return;
2482 }
2484 # This function should do some intensive task to minimize the db-traffic
2485 sub strip_packages_list_statements {
2486 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2487 my @new_statement_list=();
2488 my $hash;
2489 my $insert_hash;
2490 my $update_hash;
2491 my $delete_hash;
2492 my $local_timestamp=get_time();
2494 foreach my $existing_entry (@existing_entries) {
2495 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2496 }
2498 foreach my $statement (@packages_list_statements) {
2499 if($statement =~ /^INSERT/i) {
2500 # Assign the values from the insert statement
2501 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2502 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2503 if(exists($hash->{$distribution}->{$package}->{$version})) {
2504 # If section or description has changed, update the DB
2505 if(
2506 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2507 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2508 ) {
2509 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2510 }
2511 } else {
2512 # Insert a non-existing entry to db
2513 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2514 }
2515 } elsif ($statement =~ /^UPDATE/i) {
2516 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2517 /^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;
2518 foreach my $distribution (keys %{$hash}) {
2519 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2520 # update the insertion hash to execute only one query per package (insert instead insert+update)
2521 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2522 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2523 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2524 my $section;
2525 my $description;
2526 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2527 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2528 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2529 }
2530 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2531 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2532 }
2533 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2534 }
2535 }
2536 }
2537 }
2538 }
2540 # TODO: Check for orphaned entries
2542 # unroll the insert_hash
2543 foreach my $distribution (keys %{$insert_hash}) {
2544 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2545 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2546 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2547 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2548 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2549 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2550 ."'$local_timestamp')";
2551 }
2552 }
2553 }
2555 # unroll the update hash
2556 foreach my $distribution (keys %{$update_hash}) {
2557 foreach my $package (keys %{$update_hash->{$distribution}}) {
2558 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2559 my $set = "";
2560 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2561 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2562 }
2563 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2564 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2565 }
2566 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2567 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2568 }
2569 if(defined($set) and length($set) > 0) {
2570 $set .= "timestamp = '$local_timestamp'";
2571 } else {
2572 next;
2573 }
2574 push @new_statement_list,
2575 "UPDATE $main::packages_list_tn SET $set WHERE"
2576 ." distribution = '$distribution'"
2577 ." AND package = '$package'"
2578 ." AND version = '$version'";
2579 }
2580 }
2581 }
2583 @packages_list_statements = @new_statement_list;
2584 }
2587 sub parse_package_info {
2588 my ($baseurl, $dist, $section, $session_id)= @_;
2589 my ($package);
2590 if (not defined $session_id) { $session_id = 0; }
2591 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2592 $repo_dirs{ "${repo_path}/pool" } = 1;
2594 foreach $package ("Packages.gz"){
2595 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2596 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2597 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2598 }
2600 }
2603 sub get_package {
2604 my ($url, $dest, $session_id)= @_;
2605 if (not defined $session_id) { $session_id = 0; }
2607 my $tpath = dirname($dest);
2608 -d "$tpath" || mkpath "$tpath";
2610 # This is ugly, but I've no time to take a look at "how it works in perl"
2611 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2612 system("gunzip -cd '$dest' > '$dest.in'");
2613 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2614 unlink($dest);
2615 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2616 } else {
2617 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2618 }
2619 return 0;
2620 }
2623 sub parse_package {
2624 my ($path, $dist, $srv_path, $session_id)= @_;
2625 if (not defined $session_id) { $session_id = 0;}
2626 my ($package, $version, $section, $description);
2627 my $PACKAGES;
2628 my $timestamp = &get_time();
2630 if(not stat("$path.in")) {
2631 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2632 return;
2633 }
2635 open($PACKAGES, "<$path.in");
2636 if(not defined($PACKAGES)) {
2637 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2638 return;
2639 }
2641 # Read lines
2642 while (<$PACKAGES>){
2643 my $line = $_;
2644 # Unify
2645 chop($line);
2647 # Use empty lines as a trigger
2648 if ($line =~ /^\s*$/){
2649 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2650 push(@packages_list_statements, $sql);
2651 $package = "none";
2652 $version = "none";
2653 $section = "none";
2654 $description = "none";
2655 next;
2656 }
2658 # Trigger for package name
2659 if ($line =~ /^Package:\s/){
2660 ($package)= ($line =~ /^Package: (.*)$/);
2661 next;
2662 }
2664 # Trigger for version
2665 if ($line =~ /^Version:\s/){
2666 ($version)= ($line =~ /^Version: (.*)$/);
2667 next;
2668 }
2670 # Trigger for description
2671 if ($line =~ /^Description:\s/){
2672 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2673 next;
2674 }
2676 # Trigger for section
2677 if ($line =~ /^Section:\s/){
2678 ($section)= ($line =~ /^Section: (.*)$/);
2679 next;
2680 }
2682 # Trigger for filename
2683 if ($line =~ /^Filename:\s/){
2684 my ($filename) = ($line =~ /^Filename: (.*)$/);
2685 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2686 next;
2687 }
2688 }
2690 close( $PACKAGES );
2691 unlink( "$path.in" );
2692 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2693 }
2696 sub store_fileinfo {
2697 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2699 my %fileinfo = (
2700 'package' => $package,
2701 'dist' => $dist,
2702 'version' => $vers,
2703 );
2705 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2706 }
2709 sub cleanup_and_extract {
2710 my $fileinfo = $repo_files{ $File::Find::name };
2712 if( defined $fileinfo ) {
2714 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2715 my $sql;
2716 my $package = $fileinfo->{ 'package' };
2717 my $newver = $fileinfo->{ 'version' };
2719 mkpath($dir);
2720 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2722 if( -f "$dir/DEBIAN/templates" ) {
2724 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2726 my $tmpl= "";
2727 {
2728 local $/=undef;
2729 open FILE, "$dir/DEBIAN/templates";
2730 $tmpl = &encode_base64(<FILE>);
2731 close FILE;
2732 }
2733 rmtree("$dir/DEBIAN/templates");
2735 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2736 push @packages_list_statements, $sql;
2737 }
2738 }
2740 return;
2741 }
2744 sub register_at_foreign_servers {
2745 my ($kernel) = $_[KERNEL];
2747 # hole alle bekannten server aus known_server_db
2748 my $server_sql = "SELECT * FROM $known_server_tn";
2749 my $server_res = $known_server_db->exec_statement($server_sql);
2751 # no entries in known_server_db
2752 if (not ref(@$server_res[0]) eq "ARRAY") {
2753 # TODO
2754 }
2756 # detect already connected clients
2757 my $client_sql = "SELECT * FROM $known_clients_tn";
2758 my $client_res = $known_clients_db->exec_statement($client_sql);
2760 # send my server details to all other gosa-si-server within the network
2761 foreach my $hit (@$server_res) {
2762 my $hostname = @$hit[0];
2763 my $hostkey = &create_passwd;
2765 # add already connected clients to registration message
2766 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2767 &add_content2xml_hash($myhash, 'key', $hostkey);
2768 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2770 # build registration message and send it
2771 my $foreign_server_msg = &create_xml_string($myhash);
2772 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2773 }
2775 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2776 return;
2777 }
2780 #==== MAIN = main ==============================================================
2781 # parse commandline options
2782 Getopt::Long::Configure( "bundling" );
2783 GetOptions("h|help" => \&usage,
2784 "c|config=s" => \$cfg_file,
2785 "f|foreground" => \$foreground,
2786 "v|verbose+" => \$verbose,
2787 "no-arp+" => \$no_arp,
2788 );
2790 # read and set config parameters
2791 &check_cmdline_param ;
2792 &read_configfile;
2793 &check_pid;
2795 $SIG{CHLD} = 'IGNORE';
2797 # forward error messages to logfile
2798 if( ! $foreground ) {
2799 open( STDIN, '+>/dev/null' );
2800 open( STDOUT, '+>&STDIN' );
2801 open( STDERR, '+>&STDIN' );
2802 }
2804 # Just fork, if we are not in foreground mode
2805 if( ! $foreground ) {
2806 chdir '/' or die "Can't chdir to /: $!";
2807 $pid = fork;
2808 setsid or die "Can't start a new session: $!";
2809 umask 0;
2810 } else {
2811 $pid = $$;
2812 }
2814 # Do something useful - put our PID into the pid_file
2815 if( 0 != $pid ) {
2816 open( LOCK_FILE, ">$pid_file" );
2817 print LOCK_FILE "$pid\n";
2818 close( LOCK_FILE );
2819 if( !$foreground ) {
2820 exit( 0 )
2821 };
2822 }
2824 # parse head url and revision from svn
2825 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2826 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2827 $server_headURL = defined $1 ? $1 : 'unknown' ;
2828 $server_revision = defined $2 ? $2 : 'unknown' ;
2829 if ($server_headURL =~ /\/tag\// ||
2830 $server_headURL =~ /\/branches\// ) {
2831 $server_status = "stable";
2832 } else {
2833 $server_status = "developmental" ;
2834 }
2837 daemon_log(" ", 1);
2838 daemon_log("$0 started!", 1);
2839 daemon_log("status: $server_status", 1);
2840 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2842 # connect to incoming_db
2843 unlink($incoming_file_name);
2844 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2845 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2847 # connect to gosa-si job queue
2848 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2849 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2851 # connect to known_clients_db
2852 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2853 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2855 # connect to foreign_clients_db
2856 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2857 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2859 # connect to known_server_db
2860 unlink($known_server_file_name);
2861 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2862 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2864 # connect to login_usr_db
2865 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2866 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2868 # connect to fai_server_db and fai_release_db
2869 unlink($fai_server_file_name);
2870 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2871 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2873 unlink($fai_release_file_name);
2874 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2875 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2877 # connect to packages_list_db
2878 #unlink($packages_list_file_name);
2879 unlink($packages_list_under_construction);
2880 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2881 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2883 # connect to messaging_db
2884 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2885 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2888 # create xml object used for en/decrypting
2889 $xml = new XML::Simple();
2892 # foreign servers
2893 my @foreign_server_list;
2895 # add foreign server from cfg file
2896 if ($foreign_server_string ne "") {
2897 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2898 foreach my $foreign_server (@cfg_foreign_server_list) {
2899 push(@foreign_server_list, $foreign_server);
2900 }
2901 }
2903 # add foreign server from dns
2904 my @tmp_servers;
2905 if ( !$server_domain) {
2906 # Try our DNS Searchlist
2907 for my $domain(get_dns_domains()) {
2908 chomp($domain);
2909 my @tmp_domains= &get_server_addresses($domain);
2910 if(@tmp_domains) {
2911 for my $tmp_server(@tmp_domains) {
2912 push @tmp_servers, $tmp_server;
2913 }
2914 }
2915 }
2916 if(@tmp_servers && length(@tmp_servers)==0) {
2917 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2918 }
2919 } else {
2920 @tmp_servers = &get_server_addresses($server_domain);
2921 if( 0 == @tmp_servers ) {
2922 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2923 }
2924 }
2925 foreach my $server (@tmp_servers) {
2926 unshift(@foreign_server_list, $server);
2927 }
2928 # eliminate duplicate entries
2929 @foreign_server_list = &del_doubles(@foreign_server_list);
2930 my $all_foreign_server = join(", ", @foreign_server_list);
2931 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2933 # add all found foreign servers to known_server
2934 my $act_timestamp = &get_time();
2935 foreach my $foreign_server (@foreign_server_list) {
2936 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2937 primkey=>['hostname'],
2938 hostname=>$foreign_server,
2939 status=>'not_jet_registered',
2940 hostkey=>"none",
2941 timestamp=>$act_timestamp,
2942 } );
2943 }
2946 POE::Component::Server::TCP->new(
2947 Alias => "TCP_SERVER",
2948 Port => $server_port,
2949 ClientInput => sub {
2950 my ($kernel, $input) = @_[KERNEL, ARG0];
2951 push(@tasks, $input);
2952 push(@msgs_to_decrypt, $input);
2953 $kernel->yield("msg_to_decrypt");
2954 },
2955 InlineStates => {
2956 msg_to_decrypt => \&msg_to_decrypt,
2957 next_task => \&next_task,
2958 task_result => \&handle_task_result,
2959 task_done => \&handle_task_done,
2960 task_debug => \&handle_task_debug,
2961 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2962 }
2963 );
2965 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2967 # create session for repeatedly checking the job queue for jobs
2968 POE::Session->create(
2969 inline_states => {
2970 _start => \&session_start,
2971 register_at_foreign_servers => \®ister_at_foreign_servers,
2972 sig_handler => \&sig_handler,
2973 next_task => \&next_task,
2974 task_result => \&handle_task_result,
2975 task_done => \&handle_task_done,
2976 task_debug => \&handle_task_debug,
2977 watch_for_next_tasks => \&watch_for_next_tasks,
2978 watch_for_new_messages => \&watch_for_new_messages,
2979 watch_for_delivery_messages => \&watch_for_delivery_messages,
2980 watch_for_done_messages => \&watch_for_done_messages,
2981 watch_for_new_jobs => \&watch_for_new_jobs,
2982 watch_for_done_jobs => \&watch_for_done_jobs,
2983 watch_for_old_known_clients => \&watch_for_old_known_clients,
2984 create_packages_list_db => \&run_create_packages_list_db,
2985 create_fai_server_db => \&run_create_fai_server_db,
2986 create_fai_release_db => \&run_create_fai_release_db,
2987 session_run_result => \&session_run_result,
2988 session_run_debug => \&session_run_debug,
2989 session_run_done => \&session_run_done,
2990 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2991 }
2992 );
2995 # import all modules
2996 &import_modules;
2998 # TODO
2999 # check wether all modules are gosa-si valid passwd check
3003 POE::Kernel->run();
3004 exit;