238fcd6fca51637e5a01176f7cfca1f84b35adef
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 #===============================================================================
23 use strict;
24 use warnings;
25 use Getopt::Long;
26 use Config::IniFiles;
27 use POSIX;
29 use Fcntl;
30 use IO::Socket::INET;
31 use IO::Handle;
32 use IO::Select;
33 use Symbol qw(qualify_to_ref);
34 use Crypt::Rijndael;
35 use MIME::Base64;
36 use Digest::MD5 qw(md5 md5_hex md5_base64);
37 use XML::Simple;
38 use Data::Dumper;
39 use Sys::Syslog qw( :DEFAULT setlogsock);
40 use Cwd;
41 use File::Spec;
42 use File::Basename;
43 use File::Find;
44 use File::Copy;
45 use File::Path;
46 use GOSA::DBsqlite;
47 use GOSA::GosaSupportDaemon;
48 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
49 use Net::LDAP;
50 use Net::LDAP::Util qw(:escape);
52 my $modules_path = "/usr/lib/gosa-si/modules";
53 use lib "/usr/lib/gosa-si/modules";
55 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
56 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
57 my ($server);
58 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
59 my ($known_modules);
60 my ($pid_file, $procid, $pid, $log_file);
61 my ($arp_activ, $arp_fifo);
62 my ($xml);
63 my $sources_list;
64 my $max_clients;
65 my %repo_files=();
66 my $repo_path;
67 my %repo_dirs=();
68 # variables declared in config file are always set to 'our'
69 our (%cfg_defaults, $log_file, $pid_file,
70 $server_ip, $server_port, $SIPackages_key,
71 $arp_activ, $gosa_unit_tag,
72 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
73 );
75 # additional variable which should be globaly accessable
76 our $server_address;
77 our $server_mac_address;
78 our $bus_address;
79 our $gosa_address;
80 our $no_bus;
81 our $no_arp;
82 our $verbose;
83 our $forground;
84 our $cfg_file;
85 our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
88 # specifies the verbosity of the daemon_log
89 $verbose = 0 ;
91 # if foreground is not null, script will be not forked to background
92 $foreground = 0 ;
94 # specifies the timeout seconds while checking the online status of a registrating client
95 $ping_timeout = 5;
97 $no_bus = 0;
98 $bus_activ = "true";
100 $no_arp = 0;
102 our $prg= basename($0);
104 # holds all gosa jobs
105 our $job_db;
106 our $job_queue_tn = 'jobs';
107 my $job_queue_file_name;
108 my @job_queue_col_names = ("id INTEGER",
109 "timestamp",
110 "status DEFAULT 'none'",
111 "result DEFAULT 'none'",
112 "progress DEFAULT 'none'",
113 "headertag DEFAULT 'none'",
114 "targettag DEFAULT 'none'",
115 "xmlmessage DEFAULT 'none'",
116 "macaddress DEFAULT 'none'",
117 );
119 # holds all other gosa-sd as well as the gosa-sd-bus
120 our $known_server_db;
121 our $known_server_tn = "known_server";
122 my $known_server_file_name;
123 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
125 # holds all registrated clients
126 our $known_clients_db;
127 our $known_clients_tn = "known_clients";
128 my $known_clients_file_name;
129 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
131 # holds all logged in user at each client
132 our $login_users_db;
133 our $login_users_tn = "login_users";
134 my $login_users_file_name;
135 my @login_users_col_names = ('client', 'user', 'timestamp');
137 # holds all fai server, the debian release and tag
138 our $fai_server_db;
139 our $fai_server_tn = "fai_server";
140 my $fai_server_file_name;
141 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag');
142 our $fai_release_tn = "fai_release";
143 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state');
145 # holds all packages available from different repositories
146 our $packages_list_db;
147 our $packages_list_tn = "packages_list";
148 my $packages_list_file_name;
149 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
150 my $outdir = "/tmp/packages_list_db";
151 my $arch = "i386";
153 # holds all messages which should be delivered to a user
154 our $messaging_db;
155 our $messaging_tn = "messaging";
156 our @messaging_col_names = ('subject', 'from', 'to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER', );
157 my $messaging_file_name;
159 # path to directory to store client install log files
160 our $client_fai_log_dir = "/var/log/fai";
162 # queue which stores taskes until one of the $max_children children are ready to process the task
163 my @tasks = qw();
164 my $max_children = 2;
167 %cfg_defaults = (
168 "general" => {
169 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
170 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
171 },
172 "bus" => {
173 "activ" => [\$bus_activ, "true"],
174 },
175 "server" => {
176 "port" => [\$server_port, "20081"],
177 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
178 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
179 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
180 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
181 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
182 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
183 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
184 "repo-path" => [\$repo_path, '/srv/www/repository'],
185 "ldap-uri" => [\$ldap_uri, ""],
186 "ldap-base" => [\$ldap_base, ""],
187 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
188 "ldap-admin-password" => [\$ldap_admin_password, ""],
189 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
190 "max-clients" => [\$max_clients, 10],
191 },
192 "GOsaPackages" => {
193 "ip" => [\$gosa_ip, "0.0.0.0"],
194 "port" => [\$gosa_port, "20082"],
195 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
196 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
197 "key" => [\$GosaPackages_key, "none"],
198 },
199 "SIPackages" => {
200 "key" => [\$SIPackages_key, "none"],
201 },
202 );
205 #=== FUNCTION ================================================================
206 # NAME: usage
207 # PARAMETERS: nothing
208 # RETURNS: nothing
209 # DESCRIPTION: print out usage text to STDERR
210 #===============================================================================
211 sub usage {
212 print STDERR << "EOF" ;
213 usage: $prg [-hvf] [-c config]
215 -h : this (help) message
216 -c <file> : config file
217 -f : foreground, process will not be forked to background
218 -v : be verbose (multiple to increase verbosity)
219 -no-bus : starts $prg without connection to bus
220 -no-arp : starts $prg without connection to arp module
222 EOF
223 print "\n" ;
224 }
227 #=== FUNCTION ================================================================
228 # NAME: read_configfile
229 # PARAMETERS: cfg_file - string -
230 # RETURNS: nothing
231 # DESCRIPTION: read cfg_file and set variables
232 #===============================================================================
233 sub read_configfile {
234 my $cfg;
235 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
236 if( -r $cfg_file ) {
237 $cfg = Config::IniFiles->new( -file => $cfg_file );
238 } else {
239 print STDERR "Couldn't read config file!\n";
240 }
241 } else {
242 $cfg = Config::IniFiles->new() ;
243 }
244 foreach my $section (keys %cfg_defaults) {
245 foreach my $param (keys %{$cfg_defaults{ $section }}) {
246 my $pinfo = $cfg_defaults{ $section }{ $param };
247 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
248 }
249 }
250 }
253 #=== FUNCTION ================================================================
254 # NAME: logging
255 # PARAMETERS: level - string - default 'info'
256 # msg - string -
257 # facility - string - default 'LOG_DAEMON'
258 # RETURNS: nothing
259 # DESCRIPTION: function for logging
260 #===============================================================================
261 sub daemon_log {
262 # log into log_file
263 my( $msg, $level ) = @_;
264 if(not defined $msg) { return }
265 if(not defined $level) { $level = 1 }
266 if(defined $log_file){
267 open(LOG_HANDLE, ">>$log_file");
268 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
269 print STDERR "cannot open $log_file: $!";
270 return }
271 chomp($msg);
272 if($level <= $verbose){
273 my ($seconds, $minutes, $hours, $monthday, $month,
274 $year, $weekday, $yearday, $sommertime) = localtime(time);
275 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
276 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
277 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
278 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
279 $month = $monthnames[$month];
280 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
281 $year+=1900;
282 my $name = $prg;
284 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
285 print LOG_HANDLE $log_msg;
286 if( $foreground ) {
287 print STDERR $log_msg;
288 }
289 }
290 close( LOG_HANDLE );
291 }
292 }
295 #=== FUNCTION ================================================================
296 # NAME: check_cmdline_param
297 # PARAMETERS: nothing
298 # RETURNS: nothing
299 # DESCRIPTION: validates commandline parameter
300 #===============================================================================
301 sub check_cmdline_param () {
302 my $err_config;
303 my $err_counter = 0;
304 if(not defined($cfg_file)) {
305 $cfg_file = "/etc/gosa-si/server.conf";
306 if(! -r $cfg_file) {
307 $err_config = "please specify a config file";
308 $err_counter += 1;
309 }
310 }
311 if( $err_counter > 0 ) {
312 &usage( "", 1 );
313 if( defined( $err_config)) { print STDERR "$err_config\n"}
314 print STDERR "\n";
315 exit( -1 );
316 }
317 }
320 #=== FUNCTION ================================================================
321 # NAME: check_pid
322 # PARAMETERS: nothing
323 # RETURNS: nothing
324 # DESCRIPTION: handels pid processing
325 #===============================================================================
326 sub check_pid {
327 $pid = -1;
328 # Check, if we are already running
329 if( open(LOCK_FILE, "<$pid_file") ) {
330 $pid = <LOCK_FILE>;
331 if( defined $pid ) {
332 chomp( $pid );
333 if( -f "/proc/$pid/stat" ) {
334 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
335 if( $stat ) {
336 daemon_log("ERROR: Already running",1);
337 close( LOCK_FILE );
338 exit -1;
339 }
340 }
341 }
342 close( LOCK_FILE );
343 unlink( $pid_file );
344 }
346 # create a syslog msg if it is not to possible to open PID file
347 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
348 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
349 if (open(LOCK_FILE, '<', $pid_file)
350 && ($pid = <LOCK_FILE>))
351 {
352 chomp($pid);
353 $msg .= "(PID $pid)\n";
354 } else {
355 $msg .= "(unable to read PID)\n";
356 }
357 if( ! ($foreground) ) {
358 openlog( $0, "cons,pid", "daemon" );
359 syslog( "warning", $msg );
360 closelog();
361 }
362 else {
363 print( STDERR " $msg " );
364 }
365 exit( -1 );
366 }
367 }
369 #=== FUNCTION ================================================================
370 # NAME: import_modules
371 # PARAMETERS: module_path - string - abs. path to the directory the modules
372 # are stored
373 # RETURNS: nothing
374 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
375 # state is on is imported by "require 'file';"
376 #===============================================================================
377 sub import_modules {
378 daemon_log(" ", 1);
380 if (not -e $modules_path) {
381 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
382 }
384 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
385 while (defined (my $file = readdir (DIR))) {
386 if (not $file =~ /(\S*?).pm$/) {
387 next;
388 }
389 my $mod_name = $1;
391 if( $file =~ /ArpHandler.pm/ ) {
392 if( $no_arp > 0 ) {
393 next;
394 }
395 }
397 eval { require $file; };
398 if ($@) {
399 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
400 daemon_log("$@", 5);
401 } else {
402 my $info = eval($mod_name.'::get_module_info()');
403 # Only load module if get_module_info() returns a non-null object
404 if( $info ) {
405 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
406 $known_modules->{$mod_name} = $info;
407 daemon_log("INFO: module $mod_name loaded", 5);
408 }
409 }
410 }
411 close (DIR);
412 }
415 #=== FUNCTION ================================================================
416 # NAME: sig_int_handler
417 # PARAMETERS: signal - string - signal arose from system
418 # RETURNS: noting
419 # DESCRIPTION: handels tasks to be done befor signal becomes active
420 #===============================================================================
421 sub sig_int_handler {
422 my ($signal) = @_;
424 if(defined($ldap_handle)) {
425 $ldap_handle->disconnect;
426 }
428 daemon_log("shutting down gosa-si-server", 1);
429 system("killall gosa-si-server");
430 }
431 $SIG{INT} = \&sig_int_handler;
434 sub check_key_and_xml_validity {
435 my ($crypted_msg, $module_key, $session_id) = @_;
436 my $msg;
437 my $msg_hash;
438 my $error_string;
439 eval{
440 $msg = &decrypt_msg($crypted_msg, $module_key);
442 if ($msg =~ /<xml>/i){
443 $msg =~ s/\s+/ /g; # just for better daemon_log
444 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
445 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
447 ##############
448 # check header
449 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
450 my $header_l = $msg_hash->{'header'};
451 if( 1 > @{$header_l} ) { die 'empty header tag'; }
452 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
453 my $header = @{$header_l}[0];
454 if( 0 == length $header) { die 'empty string in header tag'; }
456 ##############
457 # check source
458 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
459 my $source_l = $msg_hash->{'source'};
460 if( 1 > @{$source_l} ) { die 'empty source tag'; }
461 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
462 my $source = @{$source_l}[0];
463 if( 0 == length $source) { die 'source error'; }
465 ##############
466 # check target
467 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
468 my $target_l = $msg_hash->{'target'};
469 if( 1 > @{$target_l} ) { die 'empty target tag'; }
470 }
471 };
472 if($@) {
473 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
474 $msg = undef;
475 $msg_hash = undef;
476 }
478 return ($msg, $msg_hash);
479 }
482 sub check_outgoing_xml_validity {
483 my ($msg) = @_;
485 my $msg_hash;
486 eval{
487 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
489 ##############
490 # check header
491 my $header_l = $msg_hash->{'header'};
492 if( 1 != @{$header_l} ) {
493 die 'no or more than one headers specified';
494 }
495 my $header = @{$header_l}[0];
496 if( 0 == length $header) {
497 die 'header has length 0';
498 }
500 ##############
501 # check source
502 my $source_l = $msg_hash->{'source'};
503 if( 1 != @{$source_l} ) {
504 die 'no or more than 1 sources specified';
505 }
506 my $source = @{$source_l}[0];
507 if( 0 == length $source) {
508 die 'source has length 0';
509 }
510 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
511 $source =~ /^GOSA$/i ) {
512 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
513 }
515 ##############
516 # check target
517 my $target_l = $msg_hash->{'target'};
518 if( 0 == @{$target_l} ) {
519 die "no targets specified";
520 }
521 foreach my $target (@$target_l) {
522 if( 0 == length $target) {
523 die "target has length 0";
524 }
525 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
526 $target =~ /^GOSA$/i ||
527 $target =~ /^\*$/ ||
528 $target =~ /KNOWN_SERVER/i ||
529 $target =~ /JOBDB/i ||
530 $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 ){
531 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
532 }
533 }
534 };
535 if($@) {
536 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
537 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
538 $msg_hash = undef;
539 }
541 return ($msg_hash);
542 }
545 sub input_from_known_server {
546 my ($input, $remote_ip, $session_id) = @_ ;
547 my ($msg, $msg_hash, $module);
549 my $sql_statement= "SELECT * FROM known_server";
550 my $query_res = $known_server_db->select_dbentry( $sql_statement );
552 while( my ($hit_num, $hit) = each %{ $query_res } ) {
553 my $host_name = $hit->{hostname};
554 if( not $host_name =~ "^$remote_ip") {
555 next;
556 }
557 my $host_key = $hit->{hostkey};
558 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
559 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
561 # check if module can open msg envelope with module key
562 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
563 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
564 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
565 daemon_log("$@", 8);
566 next;
567 }
568 else {
569 $msg = $tmp_msg;
570 $msg_hash = $tmp_msg_hash;
571 $module = "SIPackages";
572 last;
573 }
574 }
576 if( (!$msg) || (!$msg_hash) || (!$module) ) {
577 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
578 }
580 return ($msg, $msg_hash, $module);
581 }
584 sub input_from_known_client {
585 my ($input, $remote_ip, $session_id) = @_ ;
586 my ($msg, $msg_hash, $module);
588 my $sql_statement= "SELECT * FROM known_clients";
589 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
590 while( my ($hit_num, $hit) = each %{ $query_res } ) {
591 my $host_name = $hit->{hostname};
592 if( not $host_name =~ /^$remote_ip:\d*$/) {
593 next;
594 }
595 my $host_key = $hit->{hostkey};
596 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
597 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
599 # check if module can open msg envelope with module key
600 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
602 if( (!$msg) || (!$msg_hash) ) {
603 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
604 &daemon_log("$@", 8);
605 next;
606 }
607 else {
608 $module = "SIPackages";
609 last;
610 }
611 }
613 if( (!$msg) || (!$msg_hash) || (!$module) ) {
614 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
615 }
617 return ($msg, $msg_hash, $module);
618 }
621 sub input_from_unknown_host {
622 no strict "refs";
623 my ($input, $session_id) = @_ ;
624 my ($msg, $msg_hash, $module);
625 my $error_string;
627 my %act_modules = %$known_modules;
629 while( my ($mod, $info) = each(%act_modules)) {
631 # check a key exists for this module
632 my $module_key = ${$mod."_key"};
633 if( not defined $module_key ) {
634 if( $mod eq 'ArpHandler' ) {
635 next;
636 }
637 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
638 next;
639 }
640 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
642 # check if module can open msg envelope with module key
643 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
644 if( (not defined $msg) || (not defined $msg_hash) ) {
645 next;
646 }
647 else {
648 $module = $mod;
649 last;
650 }
651 }
653 if( (!$msg) || (!$msg_hash) || (!$module)) {
654 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
655 }
657 return ($msg, $msg_hash, $module);
658 }
661 sub create_ciphering {
662 my ($passwd) = @_;
663 if((!defined($passwd)) || length($passwd)==0) {
664 $passwd = "";
665 }
666 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
667 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
668 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
669 $my_cipher->set_iv($iv);
670 return $my_cipher;
671 }
674 sub encrypt_msg {
675 my ($msg, $key) = @_;
676 my $my_cipher = &create_ciphering($key);
677 my $len;
678 {
679 use bytes;
680 $len= 16-length($msg)%16;
681 }
682 $msg = "\0"x($len).$msg;
683 $msg = $my_cipher->encrypt($msg);
684 chomp($msg = &encode_base64($msg));
685 # there are no newlines allowed inside msg
686 $msg=~ s/\n//g;
687 return $msg;
688 }
691 sub decrypt_msg {
693 my ($msg, $key) = @_ ;
694 $msg = &decode_base64($msg);
695 my $my_cipher = &create_ciphering($key);
696 $msg = $my_cipher->decrypt($msg);
697 $msg =~ s/\0*//g;
698 return $msg;
699 }
702 sub get_encrypt_key {
703 my ($target) = @_ ;
704 my $encrypt_key;
705 my $error = 0;
707 # target can be in known_server
708 if( not defined $encrypt_key ) {
709 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
710 my $query_res = $known_server_db->select_dbentry( $sql_statement );
711 while( my ($hit_num, $hit) = each %{ $query_res } ) {
712 my $host_name = $hit->{hostname};
713 if( $host_name ne $target ) {
714 next;
715 }
716 $encrypt_key = $hit->{hostkey};
717 last;
718 }
719 }
721 # target can be in known_client
722 if( not defined $encrypt_key ) {
723 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
724 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
725 while( my ($hit_num, $hit) = each %{ $query_res } ) {
726 my $host_name = $hit->{hostname};
727 if( $host_name ne $target ) {
728 next;
729 }
730 $encrypt_key = $hit->{hostkey};
731 last;
732 }
733 }
735 return $encrypt_key;
736 }
739 #=== FUNCTION ================================================================
740 # NAME: open_socket
741 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
742 # [PeerPort] string necessary if port not appended by PeerAddr
743 # RETURNS: socket IO::Socket::INET
744 # DESCRIPTION: open a socket to PeerAddr
745 #===============================================================================
746 sub open_socket {
747 my ($PeerAddr, $PeerPort) = @_ ;
748 if(defined($PeerPort)){
749 $PeerAddr = $PeerAddr.":".$PeerPort;
750 }
751 my $socket;
752 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
753 Porto => "tcp",
754 Type => SOCK_STREAM,
755 Timeout => 5,
756 );
757 if(not defined $socket) {
758 return;
759 }
760 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
761 return $socket;
762 }
765 #=== FUNCTION ================================================================
766 # NAME: get_ip
767 # PARAMETERS: interface name (i.e. eth0)
768 # RETURNS: (ip address)
769 # DESCRIPTION: Uses ioctl to get ip address directly from system.
770 #===============================================================================
771 sub get_ip {
772 my $ifreq= shift;
773 my $result= "";
774 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
775 my $proto= getprotobyname('ip');
777 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
778 or die "socket: $!";
780 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
781 my ($if, $sin) = unpack 'a16 a16', $ifreq;
782 my ($port, $addr) = sockaddr_in $sin;
783 my $ip = inet_ntoa $addr;
785 if ($ip && length($ip) > 0) {
786 $result = $ip;
787 }
788 }
790 return $result;
791 }
794 sub get_local_ip_for_remote_ip {
795 my $remote_ip= shift;
796 my $result="0.0.0.0";
798 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
799 if($remote_ip eq "127.0.0.1") {
800 $result = "127.0.0.1";
801 } else {
802 my $PROC_NET_ROUTE= ('/proc/net/route');
804 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
805 or die "Could not open $PROC_NET_ROUTE";
807 my @ifs = <PROC_NET_ROUTE>;
809 close(PROC_NET_ROUTE);
811 # Eat header line
812 shift @ifs;
813 chomp @ifs;
814 foreach my $line(@ifs) {
815 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
816 my $destination;
817 my $mask;
818 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
819 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
820 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
821 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
822 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
823 # destination matches route, save mac and exit
824 $result= &get_ip($Iface);
825 last;
826 }
827 }
828 }
829 } else {
830 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
831 }
832 return $result;
833 }
836 sub send_msg_to_target {
837 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
838 my $error = 0;
839 my $header;
840 my $new_status;
841 my $act_status;
842 my ($sql_statement, $res);
844 if( $msg_header ) {
845 $header = "'$msg_header'-";
846 } else {
847 $header = "";
848 }
850 # Patch the source ip
851 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
852 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
853 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
854 }
856 # encrypt xml msg
857 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
859 # opensocket
860 my $socket = &open_socket($address);
861 if( !$socket ) {
862 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
863 $error++;
864 }
866 if( $error == 0 ) {
867 # send xml msg
868 print $socket $crypted_msg."\n";
870 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
871 #daemon_log("DEBUG: message:\n$msg", 9);
873 }
875 # close socket in any case
876 if( $socket ) {
877 close $socket;
878 }
880 if( $error > 0 ) { $new_status = "down"; }
881 else { $new_status = $msg_header; }
884 # known_clients
885 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
886 $res = $known_clients_db->select_dbentry($sql_statement);
887 if( keys(%$res) > 0) {
888 $act_status = $res->{1}->{'status'};
889 if( $act_status eq "down" ) {
890 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
891 $res = $known_clients_db->del_dbentry($sql_statement);
892 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
893 } else {
894 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
895 $res = $known_clients_db->update_dbentry($sql_statement);
896 if($new_status eq "down"){
897 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
898 } else {
899 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
900 }
901 }
902 }
904 # known_server
905 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
906 $res = $known_server_db->select_dbentry($sql_statement);
907 if( keys(%$res) > 0 ) {
908 $act_status = $res->{1}->{'status'};
909 if( $act_status eq "down" ) {
910 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
911 $res = $known_server_db->del_dbentry($sql_statement);
912 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
913 }
914 else {
915 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
916 $res = $known_server_db->update_dbentry($sql_statement);
917 if($new_status eq "down"){
918 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
919 }
920 else {
921 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
922 }
923 }
924 }
925 return $error;
926 }
929 sub update_jobdb_status_for_send_msgs {
930 my ($answer, $error) = @_;
931 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
932 my $jobdb_id = $1;
934 # sending msg faild
935 if( $error ) {
936 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
937 my $sql_statement = "UPDATE $job_queue_tn ".
938 "SET status='error', result='can not deliver msg, please consult log file' ".
939 "WHERE id='$jobdb_id'";
940 my $res = $job_db->update_dbentry($sql_statement);
941 }
943 # sending msg was successful
944 } else {
945 my $sql_statement = "UPDATE $job_queue_tn ".
946 "SET status='done' ".
947 "WHERE id='$jobdb_id' AND status='processed'";
948 my $res = $job_db->update_dbentry($sql_statement);
949 }
950 }
951 }
953 sub _start {
954 my ($kernel) = $_[KERNEL];
955 &trigger_db_loop($kernel);
956 $kernel->yield('create_fai_server_db', $fai_server_tn );
957 $kernel->yield('create_fai_release_db', $fai_release_tn );
958 $kernel->sig(USR1 => "sig_handler");
959 }
961 sub sig_handler {
962 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
963 daemon_log("0 INFO got signal '$signal'", 1);
964 $kernel->sig_handled();
965 return;
966 }
968 sub next_task {
969 my ($session, $heap) = @_[SESSION, HEAP];
971 while ( keys( %{ $heap->{task} } ) < $max_children ) {
972 my $next_task = shift @tasks;
973 last unless defined $next_task;
975 my $task = POE::Wheel::Run->new(
976 Program => sub { process_task($session, $heap, $next_task) },
977 StdioFilter => POE::Filter::Reference->new(),
978 StdoutEvent => "task_result",
979 StderrEvent => "task_debug",
980 CloseEvent => "task_done",
981 );
983 $heap->{task}->{ $task->ID } = $task;
984 }
985 }
987 sub handle_task_result {
988 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
989 my $client_answer = $result->{'answer'};
990 if( $client_answer =~ s/session_id=(\d+)$// ) {
991 my $session_id = $1;
992 if( defined $session_id ) {
993 my $session_reference = $kernel->ID_id_to_session($session_id);
994 if( defined $session_reference ) {
995 $heap = $session_reference->get_heap();
996 }
997 }
999 if(exists $heap->{'client'}) {
1000 $heap->{'client'}->put($client_answer);
1001 }
1002 }
1003 $kernel->sig(CHLD => "child_reap");
1004 }
1006 sub handle_task_debug {
1007 my $result = $_[ARG0];
1008 print STDERR "$result\n";
1009 }
1011 sub handle_task_done {
1012 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1013 delete $heap->{task}->{$task_id};
1014 $kernel->yield("next_task");
1015 }
1017 sub process_task {
1018 no strict "refs";
1019 my ($session, $heap, $input) = @_;
1020 my $session_id = $session->ID;
1021 my ($msg, $msg_hash, $module);
1022 my $error = 0;
1023 my $answer_l;
1024 my ($answer_header, @answer_target_l, $answer_source);
1025 my $client_answer = "";
1027 daemon_log("", 5);
1028 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1029 daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1031 ####################
1032 # check incoming msg
1033 # msg is from a new client or gosa
1034 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1035 # msg is from a gosa-si-server or gosa-si-bus
1036 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1038 }
1039 # msg is from a gosa-si-client
1040 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1042 }
1043 # an error occurred
1044 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1045 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1046 # could not understand a msg from its server the client cause a re-registering process
1047 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1048 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1049 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1050 my $host_name = $hit->{'hostname'};
1051 my $host_key = $hit->{'hostkey'};
1052 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1053 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1054 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1055 }
1056 $error++;
1057 }
1059 ######################
1060 # process incoming msg
1061 if( $error == 0) {
1062 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1063 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1064 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1066 if ( 0 < @{$answer_l} ) {
1067 my $answer_str = join("\n", @{$answer_l});
1068 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1069 }
1070 }
1071 if( !$answer_l ) { $error++ };
1073 ########
1074 # answer
1075 if( $error == 0 ) {
1077 foreach my $answer ( @{$answer_l} ) {
1078 # for each answer in answer list
1080 # check outgoing msg to xml validity
1081 my $answer_hash = &check_outgoing_xml_validity($answer);
1082 if( not defined $answer_hash ) {
1083 next;
1084 }
1086 $answer_header = @{$answer_hash->{'header'}}[0];
1087 @answer_target_l = @{$answer_hash->{'target'}};
1088 $answer_source = @{$answer_hash->{'source'}}[0];
1090 # deliver msg to all targets
1091 foreach my $answer_target ( @answer_target_l ) {
1093 # targets of msg are all gosa-si-clients in known_clients_db
1094 if( $answer_target eq "*" ) {
1095 # answer is for all clients
1096 my $sql_statement= "SELECT * FROM known_clients";
1097 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1098 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1099 my $host_name = $hit->{hostname};
1100 my $host_key = $hit->{hostkey};
1101 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1102 &update_jobdb_status_for_send_msgs($answer, $error);
1103 }
1104 }
1106 # targets of msg are all gosa-si-server in known_server_db
1107 elsif( $answer_target eq "KNOWN_SERVER" ) {
1108 # answer is for all server in known_server
1109 my $sql_statement= "SELECT * FROM known_server";
1110 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1111 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1112 my $host_name = $hit->{hostname};
1113 my $host_key = $hit->{hostkey};
1114 $answer =~ s/KNOWN_SERVER/$host_name/g;
1115 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1116 &update_jobdb_status_for_send_msgs($answer, $error);
1117 }
1118 }
1120 # target of msg is GOsa
1121 elsif( $answer_target eq "GOSA" ) {
1122 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1123 my $add_on = "";
1124 if( defined $session_id ) {
1125 $add_on = ".session_id=$session_id";
1126 }
1127 # answer is for GOSA and has to returned to connected client
1128 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1129 $client_answer = $gosa_answer.$add_on;
1130 }
1132 # target of msg is job queue at this host
1133 elsif( $answer_target eq "JOBDB") {
1134 $answer =~ /<header>(\S+)<\/header>/;
1135 my $header;
1136 if( defined $1 ) { $header = $1; }
1137 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1138 &update_jobdb_status_for_send_msgs($answer, $error);
1139 }
1141 # target of msg is a mac address
1142 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 ) {
1143 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1144 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1145 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1146 my $found_ip_flag = 0;
1147 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1148 my $host_name = $hit->{hostname};
1149 my $host_key = $hit->{hostkey};
1150 $answer =~ s/$answer_target/$host_name/g;
1151 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1152 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1153 &update_jobdb_status_for_send_msgs($answer, $error);
1154 $found_ip_flag++ ;
1155 }
1156 if( $found_ip_flag == 0) {
1157 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1158 if( $bus_activ eq "true" ) {
1159 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1160 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1161 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1162 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1163 my $bus_address = $hit->{hostname};
1164 my $bus_key = $hit->{hostkey};
1165 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1166 &update_jobdb_status_for_send_msgs($answer, $error);
1167 last;
1168 }
1169 }
1171 }
1173 # answer is for one specific host
1174 } else {
1175 # get encrypt_key
1176 my $encrypt_key = &get_encrypt_key($answer_target);
1177 if( not defined $encrypt_key ) {
1178 # unknown target, forward msg to bus
1179 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1180 if( $bus_activ eq "true" ) {
1181 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1182 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1183 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1184 my $res_length = keys( %{$query_res} );
1185 if( $res_length == 0 ){
1186 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1187 "no bus found in known_server", 3);
1188 }
1189 else {
1190 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1191 my $bus_key = $hit->{hostkey};
1192 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1193 &update_jobdb_status_for_send_msgs($answer, $error);
1194 }
1195 }
1196 }
1197 next;
1198 }
1199 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1200 &update_jobdb_status_for_send_msgs($answer, $error);
1201 }
1202 }
1203 }
1204 }
1206 my $filter = POE::Filter::Reference->new();
1207 my %result = (
1208 status => "seems ok to me",
1209 answer => $client_answer,
1210 );
1212 my $output = $filter->put( [ \%result ] );
1213 print @$output;
1216 }
1219 sub trigger_db_loop {
1220 my ($kernel) = @_ ;
1221 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1222 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1223 }
1225 sub watch_for_done_jobs {
1226 my ($kernel,$heap) = @_[KERNEL, HEAP];
1228 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1229 " WHERE status='done'";
1230 my $res = $job_db->select_dbentry( $sql_statement );
1232 while( my ($id, $hit) = each %{$res} ) {
1233 my $jobdb_id = $hit->{id};
1234 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1235 my $res = $job_db->del_dbentry($sql_statement);
1236 }
1238 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1239 }
1241 sub watch_for_new_jobs {
1242 my ($kernel,$heap) = @_[KERNEL, HEAP];
1244 # check gosa job queue for jobs with executable timestamp
1245 my $timestamp = &get_time();
1246 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1247 " WHERE status='waiting' AND timestamp<'$timestamp'";
1248 my $res = $job_db->select_dbentry( $sql_statement );
1250 while( my ($id, $hit) = each %{$res} ) {
1251 my $jobdb_id = $hit->{id};
1252 my $macaddress = $hit->{'macaddress'};
1253 my $job_msg = $hit->{'xmlmessage'};
1254 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1255 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1256 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1257 # expect macaddress is unique!!!!!!
1258 my $target = $res_hash->{1}->{hostname};
1260 # if (not defined $target) {
1261 # &daemon_log("ERROR: no host found for mac address: $macaddress", 1);
1262 # &daemon_log("$hit->{xmlmessage}", 8);
1263 # my $sql_statement = "UPDATE $job_queue_tn ".
1264 # "SET status='error', result='no host found for mac address' ".
1265 # "WHERE id='$jobdb_id'";
1266 # my $res = $job_db->update_dbentry($sql_statement);
1267 # next;
1268 # }
1269 #
1270 # change header
1271 $job_msg =~ s/<header>job_/<header>gosa_/;
1273 # add sqlite_id
1274 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1276 $job_msg =~ /<header>(\S+)<\/header>/;
1277 my $header = $1 ;
1278 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1280 # update status in job queue to 'processing'
1281 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1282 my $res = $job_db->update_dbentry($sql_statement);
1283 }
1285 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1286 }
1289 sub refresh_ldap_handle {
1290 my $mesg;
1292 daemon_log("DEBUG: Trying to create a connection to URI $ldap_uri", 5);
1293 # Get an ldap handle, if we don't have one
1294 if( ! defined $ldap_handle ){
1295 $ldap_handle = Net::LDAP->new( $ldap_uri );
1296 }
1297 # Still not defined?
1298 if( ! defined $ldap_handle ) {
1299 daemon_log( "ch $$: Net::LDAP constructor failed: $!\n" );
1300 return 0;
1301 }
1303 # Bind to ldap server - eventually authenticate
1304 if( defined $ldap_admin_dn ) {
1305 if( defined $ldap_admin_password ) {
1306 $mesg = $ldap_handle->bind( $ldap_admin_dn, password => $ldap_admin_password );
1307 } else {
1308 $mesg = $ldap_handle->bind( $ldap_admin_dn );
1309 }
1310 } else {
1311 $mesg = $ldap_handle->bind();
1312 }
1314 if( 0 != $mesg->code ) {
1315 undef( $ldap_handle ) if( 81 == $mesg->code );
1316 daemon_log( "ch $$: LDAP bind: error (". $mesg->code . ') - ' . $mesg->error . "\n", 1);
1317 return 0;
1318 }
1320 return 1;
1321 }
1324 sub change_fai_state {
1325 my ($st, $targets, $session_id) = @_;
1326 $session_id = 0 if not defined $session_id;
1327 # Set FAI state to localboot
1328 my %mapActions= (
1329 reboot => '',
1330 update => 'softupdate',
1331 localboot => 'localboot',
1332 reinstall => 'install',
1333 rescan => '',
1334 wake => '',
1335 memcheck => 'memcheck',
1336 sysinfo => 'sysinfo',
1337 );
1339 # Return if this is unknown
1340 if (!exists $mapActions{ $st }){
1341 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1342 return;
1343 }
1345 my $state= $mapActions{ $st };
1347 &refresh_ldap_handle();
1348 if( defined($ldap_handle) ) {
1350 # Build search filter for hosts
1351 my $search= "(&(objectClass=GOhard)";
1352 foreach (@{$targets}){
1353 $search.= "(macAddress=$_)";
1354 }
1355 $search.= ")";
1357 # If there's any host inside of the search string, procress them
1358 if (!($search =~ /macAddress/)){
1359 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1360 return;
1361 }
1363 # Perform search for Unit Tag
1364 my $mesg = $ldap_handle->search(
1365 base => $ldap_base,
1366 scope => 'sub',
1367 attrs => ['dn', 'FAIstate', 'objectClass'],
1368 filter => "$search"
1369 );
1371 if ($mesg->count) {
1372 my @entries = $mesg->entries;
1373 foreach my $entry (@entries) {
1374 # Only modify entry if it is not set to '$state'
1375 if ($entry->get_value("FAIstate") ne "$state"){
1376 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1377 my $result;
1378 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1379 if (exists $tmp{'FAIobject'}){
1380 if ($state eq ''){
1381 $result= $ldap_handle->modify($entry->dn, changes => [
1382 delete => [ FAIstate => [] ] ]);
1383 } else {
1384 $result= $ldap_handle->modify($entry->dn, changes => [
1385 replace => [ FAIstate => $state ] ]);
1386 }
1387 } elsif ($state ne ''){
1388 $result= $ldap_handle->modify($entry->dn, changes => [
1389 add => [ objectClass => 'FAIobject' ],
1390 add => [ FAIstate => $state ] ]);
1391 }
1393 # Errors?
1394 if ($result->code){
1395 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1396 }
1398 } else {
1399 daemon_log("$session_id DEBUG FAIstate at host found with filter statement '$search' already at state '$st'", 7);
1400 }
1401 }
1402 }
1403 # if no ldap handle defined
1404 } else {
1405 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1406 }
1407 }
1410 sub change_goto_state {
1411 my ($st, $targets, $session_id) = @_;
1412 $session_id = 0 if not defined $session_id;
1414 # Switch on or off?
1415 my $state= $st eq 'active' ? 'active': 'locked';
1417 &refresh_ldap_handle();
1418 if( defined($ldap_handle) ) {
1420 # Build search filter for hosts
1421 my $search= "(&(objectClass=GOhard)";
1422 foreach (@{$targets}){
1423 $search.= "(macAddress=$_)";
1424 }
1425 $search.= ")";
1427 # If there's any host inside of the search string, procress them
1428 if (!($search =~ /macAddress/)){
1429 return;
1430 }
1432 # Perform search for Unit Tag
1433 my $mesg = $ldap_handle->search(
1434 base => $ldap_base,
1435 scope => 'sub',
1436 attrs => ['dn', 'gotoMode'],
1437 filter => "$search"
1438 );
1440 if ($mesg->count) {
1441 my @entries = $mesg->entries;
1442 foreach my $entry (@entries) {
1444 # Only modify entry if it is not set to '$state'
1445 if ($entry->get_value("gotoMode") ne $state){
1447 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1448 my $result;
1449 $result= $ldap_handle->modify($entry->dn, changes => [
1450 replace => [ gotoMode => $state ] ]);
1452 # Errors?
1453 if ($result->code){
1454 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1455 }
1457 }
1458 }
1459 }
1461 }
1462 }
1465 sub create_fai_server_db {
1466 my ($table_name, $kernel) = @_;
1467 my $result;
1469 if(defined($ldap_handle)) {
1470 daemon_log("INFO: create_fai_server_db: start", 5);
1471 my $mesg= $ldap_handle->search(
1472 base => $ldap_base,
1473 scope => 'sub',
1474 attrs => ['FAIrepository', 'gosaUnitTag'],
1475 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1476 );
1477 if($mesg->{'resultCode'} == 0 &&
1478 $mesg->count != 0) {
1479 foreach my $entry (@{$mesg->{entries}}) {
1480 if($entry->exists('FAIrepository')) {
1481 # Add an entry for each Repository configured for server
1482 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1483 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1484 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1485 $result= $fai_server_db->add_dbentry( {
1486 table => $table_name,
1487 primkey => ['server', 'release', 'tag'],
1488 server => $tmp_url,
1489 release => $tmp_release,
1490 sections => $tmp_sections,
1491 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1492 } );
1493 }
1494 }
1495 }
1496 }
1497 daemon_log("INFO: create_fai_server_db: finished", 5);
1499 # TODO: Find a way to post the 'create_packages_list_db' event
1500 &create_packages_list_db();
1501 }
1503 return $result;
1504 }
1506 sub run_create_fai_server_db {
1507 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1508 my $task = POE::Wheel::Run->new(
1509 Program => sub { &create_fai_server_db($table_name,$kernel) },
1510 StdoutEvent => "session_run_result",
1511 StderrEvent => "session_run_debug",
1512 CloseEvent => "session_run_done",
1513 );
1515 $heap->{task}->{ $task->ID } = $task;
1516 return;
1517 }
1520 sub create_fai_release_db {
1521 my ($table_name) = @_;
1522 my $result;
1524 if(defined($ldap_handle)) {
1525 daemon_log("INFO: create_fai_release_db: start",5);
1526 my $mesg= $ldap_handle->search(
1527 base => $ldap_base,
1528 scope => 'sub',
1529 attrs => [],
1530 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1531 );
1532 if($mesg->{'resultCode'} == 0 &&
1533 $mesg->count != 0) {
1534 # Walk through all possible FAI container ou's
1535 my @sql_list;
1536 my $timestamp= &get_time();
1537 foreach my $ou (@{$mesg->{entries}}) {
1538 my $tmp_classes= resolve_fai_classes($ou->dn);
1539 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1540 my @tmp_array=get_fai_release_entries($tmp_classes);
1541 if(@tmp_array) {
1542 foreach my $entry (@tmp_array) {
1543 if(defined($entry) && ref($entry) eq 'HASH') {
1544 my $sql=
1545 "INSERT INTO $table_name "
1546 ."(timestamp, release, class, type, state) VALUES ("
1547 .$timestamp.","
1548 ."'".$entry->{'release'}."',"
1549 ."'".$entry->{'class'}."',"
1550 ."'".$entry->{'type'}."',"
1551 ."'".$entry->{'state'}."')";
1552 push @sql_list, $sql;
1553 }
1554 }
1555 }
1556 }
1557 }
1558 daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1559 if(@sql_list) {
1560 unshift @sql_list, "DELETE FROM $table_name";
1561 $fai_server_db->exec_statementlist(\@sql_list);
1562 }
1563 daemon_log("DEBUG: Done with inserting",6);
1564 }
1565 daemon_log("INFO: create_fai_release_db: finished",5);
1566 }
1568 return $result;
1569 }
1570 sub run_create_fai_release_db {
1571 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1572 my $task = POE::Wheel::Run->new(
1573 Program => sub { &create_fai_release_db($table_name) },
1574 StdoutEvent => "session_run_result",
1575 StderrEvent => "session_run_debug",
1576 CloseEvent => "session_run_done",
1577 );
1579 $heap->{task}->{ $task->ID } = $task;
1580 return;
1581 }
1583 sub get_fai_types {
1584 my $tmp_classes = shift || return undef;
1585 my @result;
1587 foreach my $type(keys %{$tmp_classes}) {
1588 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1589 my $entry = {
1590 type => $type,
1591 state => $tmp_classes->{$type}[0],
1592 };
1593 push @result, $entry;
1594 }
1595 }
1597 return @result;
1598 }
1600 sub get_fai_state {
1601 my $result = "";
1602 my $tmp_classes = shift || return $result;
1604 foreach my $type(keys %{$tmp_classes}) {
1605 if(defined($tmp_classes->{$type}[0])) {
1606 $result = $tmp_classes->{$type}[0];
1608 # State is equal for all types in class
1609 last;
1610 }
1611 }
1613 return $result;
1614 }
1616 sub resolve_fai_classes {
1617 my $result;
1618 my $fai_base= shift;
1619 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1620 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1621 my $fai_classes;
1623 daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1624 my $mesg= $ldap_handle->search(
1625 base => $fai_base,
1626 scope => 'sub',
1627 attrs => ['cn','objectClass','FAIstate'],
1628 filter => $fai_filter,
1629 );
1630 daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1632 if($mesg->{'resultCode'} == 0 &&
1633 $mesg->count != 0) {
1634 foreach my $entry (@{$mesg->{entries}}) {
1635 if($entry->exists('cn')) {
1636 my $tmp_dn= $entry->dn();
1638 # Skip classname and ou dn parts for class
1639 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1641 # Skip classes without releases
1642 if((!defined($tmp_release)) || length($tmp_release)==0) {
1643 next;
1644 }
1646 my $tmp_cn= $entry->get_value('cn');
1647 my $tmp_state= $entry->get_value('FAIstate');
1649 my $tmp_type;
1650 # Get FAI type
1651 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1652 if(grep $_ eq $oclass, @possible_fai_classes) {
1653 $tmp_type= $oclass;
1654 last;
1655 }
1656 }
1658 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1659 # A Subrelease
1660 my @sub_releases = split(/,/, $tmp_release);
1662 # Walk through subreleases and build hash tree
1663 my $hash;
1664 while(my $tmp_sub_release = pop @sub_releases) {
1665 $hash .= "\{'$tmp_sub_release'\}->";
1666 }
1667 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1668 } else {
1669 # A branch, no subrelease
1670 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1671 }
1672 } elsif (!$entry->exists('cn')) {
1673 my $tmp_dn= $entry->dn();
1674 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1676 # Skip classes without releases
1677 if((!defined($tmp_release)) || length($tmp_release)==0) {
1678 next;
1679 }
1681 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1682 # A Subrelease
1683 my @sub_releases= split(/,/, $tmp_release);
1685 # Walk through subreleases and build hash tree
1686 my $hash;
1687 while(my $tmp_sub_release = pop @sub_releases) {
1688 $hash .= "\{'$tmp_sub_release'\}->";
1689 }
1690 # Remove the last two characters
1691 chop($hash);
1692 chop($hash);
1694 eval('$fai_classes->'.$hash.'= {}');
1695 } else {
1696 # A branch, no subrelease
1697 if(!exists($fai_classes->{$tmp_release})) {
1698 $fai_classes->{$tmp_release} = {};
1699 }
1700 }
1701 }
1702 }
1704 # The hash is complete, now we can honor the copy-on-write based missing entries
1705 foreach my $release (keys %$fai_classes) {
1706 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1707 }
1708 }
1709 return $result;
1710 }
1712 sub apply_fai_inheritance {
1713 my $fai_classes = shift || return {};
1714 my $tmp_classes;
1716 # Get the classes from the branch
1717 foreach my $class (keys %{$fai_classes}) {
1718 # Skip subreleases
1719 if($class =~ /^ou=.*$/) {
1720 next;
1721 } else {
1722 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1723 }
1724 }
1726 # Apply to each subrelease
1727 foreach my $subrelease (keys %{$fai_classes}) {
1728 if($subrelease =~ /ou=/) {
1729 foreach my $tmp_class (keys %{$tmp_classes}) {
1730 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1731 $fai_classes->{$subrelease}->{$tmp_class} =
1732 deep_copy($tmp_classes->{$tmp_class});
1733 } else {
1734 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1735 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1736 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1737 deep_copy($tmp_classes->{$tmp_class}->{$type});
1738 }
1739 }
1740 }
1741 }
1742 }
1743 }
1745 # Find subreleases in deeper levels
1746 foreach my $subrelease (keys %{$fai_classes}) {
1747 if($subrelease =~ /ou=/) {
1748 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1749 if($subsubrelease =~ /ou=/) {
1750 apply_fai_inheritance($fai_classes->{$subrelease});
1751 }
1752 }
1753 }
1754 }
1756 return $fai_classes;
1757 }
1759 sub get_fai_release_entries {
1760 my $tmp_classes = shift || return;
1761 my $parent = shift || "";
1762 my @result = shift || ();
1764 foreach my $entry (keys %{$tmp_classes}) {
1765 if(defined($entry)) {
1766 if($entry =~ /^ou=.*$/) {
1767 my $release_name = $entry;
1768 $release_name =~ s/ou=//g;
1769 if(length($parent)>0) {
1770 $release_name = $parent."/".$release_name;
1771 }
1772 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1773 foreach my $bufentry(@bufentries) {
1774 push @result, $bufentry;
1775 }
1776 } else {
1777 my @types = get_fai_types($tmp_classes->{$entry});
1778 foreach my $type (@types) {
1779 push @result,
1780 {
1781 'class' => $entry,
1782 'type' => $type->{'type'},
1783 'release' => $parent,
1784 'state' => $type->{'state'},
1785 };
1786 }
1787 }
1788 }
1789 }
1791 return @result;
1792 }
1794 sub deep_copy {
1795 my $this = shift;
1796 if (not ref $this) {
1797 $this;
1798 } elsif (ref $this eq "ARRAY") {
1799 [map deep_copy($_), @$this];
1800 } elsif (ref $this eq "HASH") {
1801 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1802 } else { die "what type is $_?" }
1803 }
1806 sub session_run_result {
1807 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1808 $kernel->sig(CHLD => "child_reap");
1809 }
1811 sub session_run_debug {
1812 my $result = $_[ARG0];
1813 print STDERR "$result\n";
1814 }
1816 sub session_run_done {
1817 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1818 delete $heap->{task}->{$task_id};
1819 }
1821 sub create_sources_list {
1822 my $result="/tmp/gosa_si_tmp_sources_list";
1824 # Remove old file
1825 if(stat($result)) {
1826 unlink($result);
1827 }
1829 my $fh;
1830 open($fh, ">$result") or return undef;
1831 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1832 my $mesg=$ldap_handle->search(
1833 base => $ldap_server_dn,
1834 scope => 'base',
1835 attrs => 'FAIrepository',
1836 filter => 'objectClass=FAIrepositoryServer'
1837 );
1838 if($mesg->count) {
1839 foreach my $entry(@{$mesg->{'entries'}}) {
1840 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1841 my $line = "deb $server $release";
1842 $sections =~ s/,/ /g;
1843 $line.= " $sections";
1844 print $fh $line."\n";
1845 }
1846 }
1847 }
1848 close($fh);
1850 return $result;
1851 }
1853 sub create_packages_list_db {
1854 my ($sources_file) = @_ || &create_sources_list;
1855 my $line;
1856 daemon_log("INFO: create_packages_list_db: start", 5);
1858 open(CONFIG, "<$sources_file") or do {
1859 daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1860 return;
1861 };
1863 # Read lines
1864 while ($line = <CONFIG>){
1865 # Unify
1866 chop($line);
1867 $line =~ s/^\s+//;
1868 $line =~ s/^\s+/ /;
1870 # Strip comments
1871 $line =~ s/#.*$//g;
1873 # Skip empty lines
1874 if ($line =~ /^\s*$/){
1875 next;
1876 }
1878 # Interpret deb line
1879 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1880 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1881 my $section;
1882 foreach $section (split(' ', $sections)){
1883 &parse_package_info( $baseurl, $dist, $section );
1884 }
1885 }
1886 }
1888 close (CONFIG);
1890 daemon_log("INFO: create_packages_list_db: finished", 5);
1891 return;
1892 }
1894 sub run_create_packages_list_db {
1895 my ($session, $heap) = @_[SESSION, HEAP];
1896 my $task = POE::Wheel::Run->new(
1897 Program => sub {&create_packages_list_db},
1898 StdoutEvent => "session_run_result",
1899 StderrEvent => "session_run_debug",
1900 CloseEvent => "session_run_done",
1901 );
1902 $heap->{task}->{ $task->ID } = $task;
1903 }
1905 sub parse_package_info {
1906 my ($baseurl, $dist, $section)= @_;
1907 my ($package);
1909 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1910 $repo_dirs{ "${repo_path}/pool" } = 1;
1912 foreach $package ("Packages.gz"){
1913 daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1914 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1915 parse_package( "$outdir/$dist/$section", $dist, $path );
1916 }
1917 find(\&cleanup_and_extract, keys( %repo_dirs ) );
1918 }
1920 sub get_package {
1921 my ($url, $dest)= @_;
1923 my $tpath = dirname($dest);
1924 -d "$tpath" || mkpath "$tpath";
1926 # This is ugly, but I've no time to take a look at "how it works in perl"
1927 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1928 system("gunzip -cd '$dest' > '$dest.in'");
1929 unlink($dest);
1930 } else {
1931 daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1932 }
1933 return 0;
1934 }
1936 sub parse_package {
1937 my ($path, $dist, $srv_path)= @_;
1938 my ($package, $version, $section, $description);
1939 my @sql_list;
1940 my $PACKAGES;
1942 if(not stat("$path.in")) {
1943 daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1944 return;
1945 }
1947 open($PACKAGES, "<$path.in");
1948 if(not defined($PACKAGES)) {
1949 daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1);
1950 return;
1951 }
1953 # Read lines
1954 while (<$PACKAGES>){
1955 my $line = $_;
1956 # Unify
1957 chop($line);
1959 # Use empty lines as a trigger
1960 if ($line =~ /^\s*$/){
1961 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1962 push(@sql_list, $sql);
1963 $package = "none";
1964 $version = "none";
1965 $section = "none";
1966 $description = "none";
1967 next;
1968 }
1970 # Trigger for package name
1971 if ($line =~ /^Package:\s/){
1972 ($package)= ($line =~ /^Package: (.*)$/);
1973 next;
1974 }
1976 # Trigger for version
1977 if ($line =~ /^Version:\s/){
1978 ($version)= ($line =~ /^Version: (.*)$/);
1979 next;
1980 }
1982 # Trigger for description
1983 if ($line =~ /^Description:\s/){
1984 ($description)= ($line =~ /^Description: (.*)$/);
1985 next;
1986 }
1988 # Trigger for section
1989 if ($line =~ /^Section:\s/){
1990 ($section)= ($line =~ /^Section: (.*)$/);
1991 next;
1992 }
1994 # Trigger for filename
1995 if ($line =~ /^Filename:\s/){
1996 my ($filename) = ($line =~ /^Filename: (.*)$/);
1997 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
1998 next;
1999 }
2000 }
2002 close( $PACKAGES );
2003 unlink( "$path.in" );
2005 $packages_list_db->exec_statementlist(\@sql_list);
2006 }
2008 sub store_fileinfo {
2009 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2011 my %fileinfo = (
2012 'package' => $package,
2013 'dist' => $dist,
2014 'version' => $vers,
2015 );
2017 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2018 }
2020 sub cleanup_and_extract {
2021 my $fileinfo = $repo_files{ $File::Find::name };
2023 if( defined $fileinfo ) {
2025 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2026 my $sql;
2027 my $package = $fileinfo->{ 'package' };
2028 my $newver = $fileinfo->{ 'version' };
2030 mkpath($dir);
2031 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2033 if( -f "$dir/DEBIAN/templates" ) {
2035 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2037 my $tmpl= "";
2038 {
2039 local $/=undef;
2040 open FILE, "$dir/DEBIAN/templates";
2041 $tmpl = &encode_base64(<FILE>);
2042 close FILE;
2043 }
2044 rmtree("$dir/DEBIAN/templates");
2046 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2048 } else {
2049 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2050 }
2052 my $res= $main::packages_list_db->update_dbentry($sql);
2053 }
2054 }
2057 #==== MAIN = main ==============================================================
2058 # parse commandline options
2059 Getopt::Long::Configure( "bundling" );
2060 GetOptions("h|help" => \&usage,
2061 "c|config=s" => \$cfg_file,
2062 "f|foreground" => \$foreground,
2063 "v|verbose+" => \$verbose,
2064 "no-bus+" => \$no_bus,
2065 "no-arp+" => \$no_arp,
2066 );
2068 # read and set config parameters
2069 &check_cmdline_param ;
2070 &read_configfile;
2071 &check_pid;
2073 $SIG{CHLD} = 'IGNORE';
2075 # forward error messages to logfile
2076 if( ! $foreground ) {
2077 open( STDIN, '+>/dev/null' );
2078 open( STDOUT, '+>&STDIN' );
2079 open( STDERR, '+>&STDIN' );
2080 }
2082 # Just fork, if we are not in foreground mode
2083 if( ! $foreground ) {
2084 chdir '/' or die "Can't chdir to /: $!";
2085 $pid = fork;
2086 setsid or die "Can't start a new session: $!";
2087 umask 0;
2088 } else {
2089 $pid = $$;
2090 }
2092 # Do something useful - put our PID into the pid_file
2093 if( 0 != $pid ) {
2094 open( LOCK_FILE, ">$pid_file" );
2095 print LOCK_FILE "$pid\n";
2096 close( LOCK_FILE );
2097 if( !$foreground ) {
2098 exit( 0 )
2099 };
2100 }
2102 daemon_log(" ", 1);
2103 daemon_log("$0 started!", 1);
2105 if ($no_bus > 0) {
2106 $bus_activ = "false"
2107 }
2111 # delete old DBsqlite lock files
2112 #unlink('/tmp/gosa_si_lock*');
2114 # connect to gosa-si job queue
2115 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2116 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2118 # connect to known_clients_db
2119 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2120 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2122 # connect to known_server_db
2123 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2124 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2126 # connect to login_usr_db
2127 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2128 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2130 # connect to fai_server_db and fai_release_db
2131 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2132 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2133 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2135 # connect to packages_list_db
2136 unlink($packages_list_file_name);
2137 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2138 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2140 # connect to messaging_db
2141 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2142 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2145 # create xml object used for en/decrypting
2146 $xml = new XML::Simple();
2148 # create socket for incoming xml messages
2150 POE::Component::Server::TCP->new(
2151 Port => $server_port,
2152 ClientInput => sub {
2153 my ($kernel, $input) = @_[KERNEL, ARG0];
2154 push(@tasks, $input);
2155 $kernel->yield("next_task");
2156 },
2157 InlineStates => {
2158 next_task => \&next_task,
2159 task_result => \&handle_task_result,
2160 task_done => \&handle_task_done,
2161 task_debug => \&handle_task_debug,
2162 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2163 }
2164 );
2166 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2168 # create session for repeatedly checking the job queue for jobs
2169 POE::Session->create(
2170 inline_states => {
2171 _start => \&_start,
2172 sig_handler => \&sig_handler,
2173 watch_for_new_jobs => \&watch_for_new_jobs,
2174 watch_for_done_jobs => \&watch_for_done_jobs,
2175 create_packages_list_db => \&run_create_packages_list_db,
2176 create_fai_server_db => \&run_create_fai_server_db,
2177 create_fai_release_db => \&run_create_fai_release_db,
2178 session_run_result => \&session_run_result,
2179 session_run_debug => \&session_run_debug,
2180 session_run_done => \&session_run_done,
2181 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2182 }
2183 );
2186 # import all modules
2187 &import_modules;
2189 # check wether all modules are gosa-si valid passwd check
2191 POE::Kernel->run();
2192 exit;