f32876de249626da6504a833e860ad5998bb644e
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 use strict;
25 use warnings;
26 use Getopt::Long;
27 use Config::IniFiles;
28 use POSIX;
30 use Fcntl;
31 use IO::Socket::INET;
32 use IO::Handle;
33 use IO::Select;
34 use Symbol qw(qualify_to_ref);
35 use Crypt::Rijndael;
36 use MIME::Base64;
37 use Digest::MD5 qw(md5 md5_hex md5_base64);
38 use XML::Simple;
39 use Data::Dumper;
40 use Sys::Syslog qw( :DEFAULT setlogsock);
41 use Cwd;
42 use File::Spec;
43 use File::Basename;
44 use File::Find;
45 use File::Copy;
46 use File::Path;
47 use GOSA::DBsqlite;
48 use GOSA::GosaSupportDaemon;
49 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
50 use Net::LDAP;
51 use Net::LDAP::Util qw(:escape);
53 my $modules_path = "/usr/lib/gosa-si/modules";
54 use lib "/usr/lib/gosa-si/modules";
56 # TODO es gibt eine globale funktion get_ldap_handle
57 # - ist in einer session dieses ldap handle schon vorhanden, wird es zurückgegeben
58 # - ist es nicht vorhanden, wird es erzeugt, im heap für spätere ldap anfragen gespeichert und zurückgegeben
59 # - sessions die kein ldap handle brauchen, sollen auch keins haben
60 # - wird eine session geschlossen, muss das ldap verbindung vorher beendet werden
61 our $global_kernel;
63 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
64 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
65 my ($server);
66 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
67 my ($known_modules);
68 my ($pid_file, $procid, $pid, $log_file);
69 my ($arp_activ, $arp_fifo);
70 my ($xml);
71 my $sources_list;
72 my $max_clients;
73 my %repo_files=();
74 my $repo_path;
75 my %repo_dirs=();
76 # variables declared in config file are always set to 'our'
77 our (%cfg_defaults, $log_file, $pid_file,
78 $server_ip, $server_port, $SIPackages_key,
79 $arp_activ, $gosa_unit_tag,
80 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
81 );
83 # additional variable which should be globaly accessable
84 our $server_address;
85 our $server_mac_address;
86 our $bus_address;
87 our $gosa_address;
88 our $no_bus;
89 our $no_arp;
90 our $verbose;
91 our $forground;
92 our $cfg_file;
93 #our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
94 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
97 # specifies the verbosity of the daemon_log
98 $verbose = 0 ;
100 # if foreground is not null, script will be not forked to background
101 $foreground = 0 ;
103 # specifies the timeout seconds while checking the online status of a registrating client
104 $ping_timeout = 5;
106 $no_bus = 0;
107 $bus_activ = "true";
109 $no_arp = 0;
111 our $prg= basename($0);
113 # holds all gosa jobs
114 our $job_db;
115 our $job_queue_tn = 'jobs';
116 my $job_queue_file_name;
117 my @job_queue_col_names = ("id INTEGER",
118 "timestamp",
119 "status DEFAULT 'none'",
120 "result DEFAULT 'none'",
121 "progress DEFAULT 'none'",
122 "headertag DEFAULT 'none'",
123 "targettag DEFAULT 'none'",
124 "xmlmessage DEFAULT 'none'",
125 "macaddress DEFAULT 'none'",
126 );
128 # holds all other gosa-sd as well as the gosa-sd-bus
129 our $known_server_db;
130 our $known_server_tn = "known_server";
131 my $known_server_file_name;
132 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
134 # holds all registrated clients
135 our $known_clients_db;
136 our $known_clients_tn = "known_clients";
137 my $known_clients_file_name;
138 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
140 # holds all logged in user at each client
141 our $login_users_db;
142 our $login_users_tn = "login_users";
143 my $login_users_file_name;
144 my @login_users_col_names = ('client', 'user', 'timestamp');
146 # holds all fai server, the debian release and tag
147 our $fai_server_db;
148 our $fai_server_tn = "fai_server";
149 my $fai_server_file_name;
150 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag');
151 our $fai_release_tn = "fai_release";
152 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state');
154 # holds all packages available from different repositories
155 our $packages_list_db;
156 our $packages_list_tn = "packages_list";
157 my $packages_list_file_name;
158 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
159 my $outdir = "/tmp/packages_list_db";
160 my $arch = "i386";
162 # holds all messages which should be delivered to a user
163 our $messaging_db;
164 our $messaging_tn = "messaging";
165 our @messaging_col_names = ('subject', 'from', 'to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER', );
166 my $messaging_file_name;
168 # path to directory to store client install log files
169 our $client_fai_log_dir = "/var/log/fai";
171 # queue which stores taskes until one of the $max_children children are ready to process the task
172 my @tasks = qw();
173 my $max_children = 2;
176 %cfg_defaults = (
177 "general" => {
178 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
179 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
180 },
181 "bus" => {
182 "activ" => [\$bus_activ, "true"],
183 },
184 "server" => {
185 "port" => [\$server_port, "20081"],
186 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
187 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
188 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
189 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
190 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
191 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
192 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
193 "repo-path" => [\$repo_path, '/srv/www/repository'],
194 "ldap-uri" => [\$ldap_uri, ""],
195 "ldap-base" => [\$ldap_base, ""],
196 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
197 "ldap-admin-password" => [\$ldap_admin_password, ""],
198 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
199 "max-clients" => [\$max_clients, 10],
200 },
201 "GOsaPackages" => {
202 "ip" => [\$gosa_ip, "0.0.0.0"],
203 "port" => [\$gosa_port, "20082"],
204 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
205 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
206 "key" => [\$GosaPackages_key, "none"],
207 },
208 "SIPackages" => {
209 "key" => [\$SIPackages_key, "none"],
210 },
211 );
214 #=== FUNCTION ================================================================
215 # NAME: usage
216 # PARAMETERS: nothing
217 # RETURNS: nothing
218 # DESCRIPTION: print out usage text to STDERR
219 #===============================================================================
220 sub usage {
221 print STDERR << "EOF" ;
222 usage: $prg [-hvf] [-c config]
224 -h : this (help) message
225 -c <file> : config file
226 -f : foreground, process will not be forked to background
227 -v : be verbose (multiple to increase verbosity)
228 -no-bus : starts $prg without connection to bus
229 -no-arp : starts $prg without connection to arp module
231 EOF
232 print "\n" ;
233 }
236 #=== FUNCTION ================================================================
237 # NAME: read_configfile
238 # PARAMETERS: cfg_file - string -
239 # RETURNS: nothing
240 # DESCRIPTION: read cfg_file and set variables
241 #===============================================================================
242 sub read_configfile {
243 my $cfg;
244 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
245 if( -r $cfg_file ) {
246 $cfg = Config::IniFiles->new( -file => $cfg_file );
247 } else {
248 print STDERR "Couldn't read config file!\n";
249 }
250 } else {
251 $cfg = Config::IniFiles->new() ;
252 }
253 foreach my $section (keys %cfg_defaults) {
254 foreach my $param (keys %{$cfg_defaults{ $section }}) {
255 my $pinfo = $cfg_defaults{ $section }{ $param };
256 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
257 }
258 }
259 }
262 #=== FUNCTION ================================================================
263 # NAME: logging
264 # PARAMETERS: level - string - default 'info'
265 # msg - string -
266 # facility - string - default 'LOG_DAEMON'
267 # RETURNS: nothing
268 # DESCRIPTION: function for logging
269 #===============================================================================
270 sub daemon_log {
271 # log into log_file
272 my( $msg, $level ) = @_;
273 if(not defined $msg) { return }
274 if(not defined $level) { $level = 1 }
275 if(defined $log_file){
276 open(LOG_HANDLE, ">>$log_file");
277 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
278 print STDERR "cannot open $log_file: $!";
279 return }
280 chomp($msg);
281 if($level <= $verbose){
282 my ($seconds, $minutes, $hours, $monthday, $month,
283 $year, $weekday, $yearday, $sommertime) = localtime(time);
284 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
285 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
286 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
287 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
288 $month = $monthnames[$month];
289 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
290 $year+=1900;
291 my $name = $prg;
293 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
294 print LOG_HANDLE $log_msg;
295 if( $foreground ) {
296 print STDERR $log_msg;
297 }
298 }
299 close( LOG_HANDLE );
300 }
301 }
304 #=== FUNCTION ================================================================
305 # NAME: check_cmdline_param
306 # PARAMETERS: nothing
307 # RETURNS: nothing
308 # DESCRIPTION: validates commandline parameter
309 #===============================================================================
310 sub check_cmdline_param () {
311 my $err_config;
312 my $err_counter = 0;
313 if(not defined($cfg_file)) {
314 $cfg_file = "/etc/gosa-si/server.conf";
315 if(! -r $cfg_file) {
316 $err_config = "please specify a config file";
317 $err_counter += 1;
318 }
319 }
320 if( $err_counter > 0 ) {
321 &usage( "", 1 );
322 if( defined( $err_config)) { print STDERR "$err_config\n"}
323 print STDERR "\n";
324 exit( -1 );
325 }
326 }
329 #=== FUNCTION ================================================================
330 # NAME: check_pid
331 # PARAMETERS: nothing
332 # RETURNS: nothing
333 # DESCRIPTION: handels pid processing
334 #===============================================================================
335 sub check_pid {
336 $pid = -1;
337 # Check, if we are already running
338 if( open(LOCK_FILE, "<$pid_file") ) {
339 $pid = <LOCK_FILE>;
340 if( defined $pid ) {
341 chomp( $pid );
342 if( -f "/proc/$pid/stat" ) {
343 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
344 if( $stat ) {
345 daemon_log("ERROR: Already running",1);
346 close( LOCK_FILE );
347 exit -1;
348 }
349 }
350 }
351 close( LOCK_FILE );
352 unlink( $pid_file );
353 }
355 # create a syslog msg if it is not to possible to open PID file
356 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
357 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
358 if (open(LOCK_FILE, '<', $pid_file)
359 && ($pid = <LOCK_FILE>))
360 {
361 chomp($pid);
362 $msg .= "(PID $pid)\n";
363 } else {
364 $msg .= "(unable to read PID)\n";
365 }
366 if( ! ($foreground) ) {
367 openlog( $0, "cons,pid", "daemon" );
368 syslog( "warning", $msg );
369 closelog();
370 }
371 else {
372 print( STDERR " $msg " );
373 }
374 exit( -1 );
375 }
376 }
378 #=== FUNCTION ================================================================
379 # NAME: import_modules
380 # PARAMETERS: module_path - string - abs. path to the directory the modules
381 # are stored
382 # RETURNS: nothing
383 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
384 # state is on is imported by "require 'file';"
385 #===============================================================================
386 sub import_modules {
387 daemon_log(" ", 1);
389 if (not -e $modules_path) {
390 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
391 }
393 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
394 while (defined (my $file = readdir (DIR))) {
395 if (not $file =~ /(\S*?).pm$/) {
396 next;
397 }
398 my $mod_name = $1;
400 if( $file =~ /ArpHandler.pm/ ) {
401 if( $no_arp > 0 ) {
402 next;
403 }
404 }
406 eval { require $file; };
407 if ($@) {
408 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
409 daemon_log("$@", 5);
410 } else {
411 my $info = eval($mod_name.'::get_module_info()');
412 # Only load module if get_module_info() returns a non-null object
413 if( $info ) {
414 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
415 $known_modules->{$mod_name} = $info;
416 daemon_log("INFO: module $mod_name loaded", 5);
417 }
418 }
419 }
420 close (DIR);
421 }
424 #=== FUNCTION ================================================================
425 # NAME: sig_int_handler
426 # PARAMETERS: signal - string - signal arose from system
427 # RETURNS: noting
428 # DESCRIPTION: handels tasks to be done befor signal becomes active
429 #===============================================================================
430 sub sig_int_handler {
431 my ($signal) = @_;
433 # if (defined($ldap_handle)) {
434 # $ldap_handle->disconnect;
435 # }
436 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
439 daemon_log("shutting down gosa-si-server", 1);
440 system("kill `ps -C gosa-si-server -o pid=`");
441 }
442 $SIG{INT} = \&sig_int_handler;
445 sub check_key_and_xml_validity {
446 my ($crypted_msg, $module_key, $session_id) = @_;
447 my $msg;
448 my $msg_hash;
449 my $error_string;
450 eval{
451 $msg = &decrypt_msg($crypted_msg, $module_key);
453 if ($msg =~ /<xml>/i){
454 $msg =~ s/\s+/ /g; # just for better daemon_log
455 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
456 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
458 ##############
459 # check header
460 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
461 my $header_l = $msg_hash->{'header'};
462 if( 1 > @{$header_l} ) { die 'empty header tag'; }
463 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
464 my $header = @{$header_l}[0];
465 if( 0 == length $header) { die 'empty string in header tag'; }
467 ##############
468 # check source
469 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
470 my $source_l = $msg_hash->{'source'};
471 if( 1 > @{$source_l} ) { die 'empty source tag'; }
472 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
473 my $source = @{$source_l}[0];
474 if( 0 == length $source) { die 'source error'; }
476 ##############
477 # check target
478 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
479 my $target_l = $msg_hash->{'target'};
480 if( 1 > @{$target_l} ) { die 'empty target tag'; }
481 }
482 };
483 if($@) {
484 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
485 $msg = undef;
486 $msg_hash = undef;
487 }
489 return ($msg, $msg_hash);
490 }
493 sub check_outgoing_xml_validity {
494 my ($msg) = @_;
496 my $msg_hash;
497 eval{
498 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
500 ##############
501 # check header
502 my $header_l = $msg_hash->{'header'};
503 if( 1 != @{$header_l} ) {
504 die 'no or more than one headers specified';
505 }
506 my $header = @{$header_l}[0];
507 if( 0 == length $header) {
508 die 'header has length 0';
509 }
511 ##############
512 # check source
513 my $source_l = $msg_hash->{'source'};
514 if( 1 != @{$source_l} ) {
515 die 'no or more than 1 sources specified';
516 }
517 my $source = @{$source_l}[0];
518 if( 0 == length $source) {
519 die 'source has length 0';
520 }
521 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
522 $source =~ /^GOSA$/i ) {
523 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
524 }
526 ##############
527 # check target
528 my $target_l = $msg_hash->{'target'};
529 if( 0 == @{$target_l} ) {
530 die "no targets specified";
531 }
532 foreach my $target (@$target_l) {
533 if( 0 == length $target) {
534 die "target has length 0";
535 }
536 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
537 $target =~ /^GOSA$/i ||
538 $target =~ /^\*$/ ||
539 $target =~ /KNOWN_SERVER/i ||
540 $target =~ /JOBDB/i ||
541 $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 ){
542 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
543 }
544 }
545 };
546 if($@) {
547 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
548 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
549 $msg_hash = undef;
550 }
552 return ($msg_hash);
553 }
556 sub input_from_known_server {
557 my ($input, $remote_ip, $session_id) = @_ ;
558 my ($msg, $msg_hash, $module);
560 my $sql_statement= "SELECT * FROM known_server";
561 my $query_res = $known_server_db->select_dbentry( $sql_statement );
563 while( my ($hit_num, $hit) = each %{ $query_res } ) {
564 my $host_name = $hit->{hostname};
565 if( not $host_name =~ "^$remote_ip") {
566 next;
567 }
568 my $host_key = $hit->{hostkey};
569 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
570 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
572 # check if module can open msg envelope with module key
573 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
574 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
575 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
576 daemon_log("$@", 8);
577 next;
578 }
579 else {
580 $msg = $tmp_msg;
581 $msg_hash = $tmp_msg_hash;
582 $module = "SIPackages";
583 last;
584 }
585 }
587 if( (!$msg) || (!$msg_hash) || (!$module) ) {
588 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
589 }
591 return ($msg, $msg_hash, $module);
592 }
595 sub input_from_known_client {
596 my ($input, $remote_ip, $session_id) = @_ ;
597 my ($msg, $msg_hash, $module);
599 my $sql_statement= "SELECT * FROM known_clients";
600 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
601 while( my ($hit_num, $hit) = each %{ $query_res } ) {
602 my $host_name = $hit->{hostname};
603 if( not $host_name =~ /^$remote_ip:\d*$/) {
604 next;
605 }
606 my $host_key = $hit->{hostkey};
607 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
608 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
610 # check if module can open msg envelope with module key
611 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
613 if( (!$msg) || (!$msg_hash) ) {
614 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
615 &daemon_log("$@", 8);
616 next;
617 }
618 else {
619 $module = "SIPackages";
620 last;
621 }
622 }
624 if( (!$msg) || (!$msg_hash) || (!$module) ) {
625 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
626 }
628 return ($msg, $msg_hash, $module);
629 }
632 sub input_from_unknown_host {
633 no strict "refs";
634 my ($input, $session_id) = @_ ;
635 my ($msg, $msg_hash, $module);
636 my $error_string;
638 my %act_modules = %$known_modules;
640 while( my ($mod, $info) = each(%act_modules)) {
642 # check a key exists for this module
643 my $module_key = ${$mod."_key"};
644 if( not defined $module_key ) {
645 if( $mod eq 'ArpHandler' ) {
646 next;
647 }
648 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
649 next;
650 }
651 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
653 # check if module can open msg envelope with module key
654 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
655 if( (not defined $msg) || (not defined $msg_hash) ) {
656 next;
657 }
658 else {
659 $module = $mod;
660 last;
661 }
662 }
664 if( (!$msg) || (!$msg_hash) || (!$module)) {
665 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
666 }
668 return ($msg, $msg_hash, $module);
669 }
672 sub create_ciphering {
673 my ($passwd) = @_;
674 if((!defined($passwd)) || length($passwd)==0) {
675 $passwd = "";
676 }
677 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
678 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
679 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
680 $my_cipher->set_iv($iv);
681 return $my_cipher;
682 }
685 sub encrypt_msg {
686 my ($msg, $key) = @_;
687 my $my_cipher = &create_ciphering($key);
688 my $len;
689 {
690 use bytes;
691 $len= 16-length($msg)%16;
692 }
693 $msg = "\0"x($len).$msg;
694 $msg = $my_cipher->encrypt($msg);
695 chomp($msg = &encode_base64($msg));
696 # there are no newlines allowed inside msg
697 $msg=~ s/\n//g;
698 return $msg;
699 }
702 sub decrypt_msg {
704 my ($msg, $key) = @_ ;
705 $msg = &decode_base64($msg);
706 my $my_cipher = &create_ciphering($key);
707 $msg = $my_cipher->decrypt($msg);
708 $msg =~ s/\0*//g;
709 return $msg;
710 }
713 sub get_encrypt_key {
714 my ($target) = @_ ;
715 my $encrypt_key;
716 my $error = 0;
718 # target can be in known_server
719 if( not defined $encrypt_key ) {
720 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
721 my $query_res = $known_server_db->select_dbentry( $sql_statement );
722 while( my ($hit_num, $hit) = each %{ $query_res } ) {
723 my $host_name = $hit->{hostname};
724 if( $host_name ne $target ) {
725 next;
726 }
727 $encrypt_key = $hit->{hostkey};
728 last;
729 }
730 }
732 # target can be in known_client
733 if( not defined $encrypt_key ) {
734 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
735 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
736 while( my ($hit_num, $hit) = each %{ $query_res } ) {
737 my $host_name = $hit->{hostname};
738 if( $host_name ne $target ) {
739 next;
740 }
741 $encrypt_key = $hit->{hostkey};
742 last;
743 }
744 }
746 return $encrypt_key;
747 }
750 #=== FUNCTION ================================================================
751 # NAME: open_socket
752 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
753 # [PeerPort] string necessary if port not appended by PeerAddr
754 # RETURNS: socket IO::Socket::INET
755 # DESCRIPTION: open a socket to PeerAddr
756 #===============================================================================
757 sub open_socket {
758 my ($PeerAddr, $PeerPort) = @_ ;
759 if(defined($PeerPort)){
760 $PeerAddr = $PeerAddr.":".$PeerPort;
761 }
762 my $socket;
763 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
764 Porto => "tcp",
765 Type => SOCK_STREAM,
766 Timeout => 5,
767 );
768 if(not defined $socket) {
769 return;
770 }
771 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
772 return $socket;
773 }
776 #=== FUNCTION ================================================================
777 # NAME: get_ip
778 # PARAMETERS: interface name (i.e. eth0)
779 # RETURNS: (ip address)
780 # DESCRIPTION: Uses ioctl to get ip address directly from system.
781 #===============================================================================
782 sub get_ip {
783 my $ifreq= shift;
784 my $result= "";
785 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
786 my $proto= getprotobyname('ip');
788 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
789 or die "socket: $!";
791 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
792 my ($if, $sin) = unpack 'a16 a16', $ifreq;
793 my ($port, $addr) = sockaddr_in $sin;
794 my $ip = inet_ntoa $addr;
796 if ($ip && length($ip) > 0) {
797 $result = $ip;
798 }
799 }
801 return $result;
802 }
805 sub get_local_ip_for_remote_ip {
806 my $remote_ip= shift;
807 my $result="0.0.0.0";
809 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
810 if($remote_ip eq "127.0.0.1") {
811 $result = "127.0.0.1";
812 } else {
813 my $PROC_NET_ROUTE= ('/proc/net/route');
815 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
816 or die "Could not open $PROC_NET_ROUTE";
818 my @ifs = <PROC_NET_ROUTE>;
820 close(PROC_NET_ROUTE);
822 # Eat header line
823 shift @ifs;
824 chomp @ifs;
825 foreach my $line(@ifs) {
826 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
827 my $destination;
828 my $mask;
829 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
830 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
831 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
832 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
833 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
834 # destination matches route, save mac and exit
835 $result= &get_ip($Iface);
836 last;
837 }
838 }
839 }
840 } else {
841 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
842 }
843 return $result;
844 }
847 sub send_msg_to_target {
848 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
849 my $error = 0;
850 my $header;
851 my $new_status;
852 my $act_status;
853 my ($sql_statement, $res);
855 if( $msg_header ) {
856 $header = "'$msg_header'-";
857 } else {
858 $header = "";
859 }
861 # Patch the source ip
862 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
863 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
864 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
865 }
867 # encrypt xml msg
868 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
870 # opensocket
871 my $socket = &open_socket($address);
872 if( !$socket ) {
873 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
874 $error++;
875 }
877 if( $error == 0 ) {
878 # send xml msg
879 print $socket $crypted_msg."\n";
881 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
882 #daemon_log("DEBUG: message:\n$msg", 9);
884 }
886 # close socket in any case
887 if( $socket ) {
888 close $socket;
889 }
891 if( $error > 0 ) { $new_status = "down"; }
892 else { $new_status = $msg_header; }
895 # known_clients
896 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
897 $res = $known_clients_db->select_dbentry($sql_statement);
898 if( keys(%$res) > 0) {
899 $act_status = $res->{1}->{'status'};
900 if( $act_status eq "down" ) {
901 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
902 $res = $known_clients_db->del_dbentry($sql_statement);
903 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
904 } else {
905 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
906 $res = $known_clients_db->update_dbentry($sql_statement);
907 if($new_status eq "down"){
908 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
909 } else {
910 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
911 }
912 }
913 }
915 # known_server
916 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
917 $res = $known_server_db->select_dbentry($sql_statement);
918 if( keys(%$res) > 0 ) {
919 $act_status = $res->{1}->{'status'};
920 if( $act_status eq "down" ) {
921 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
922 $res = $known_server_db->del_dbentry($sql_statement);
923 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
924 }
925 else {
926 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
927 $res = $known_server_db->update_dbentry($sql_statement);
928 if($new_status eq "down"){
929 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
930 }
931 else {
932 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
933 }
934 }
935 }
936 return $error;
937 }
940 sub update_jobdb_status_for_send_msgs {
941 my ($answer, $error) = @_;
942 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
943 my $jobdb_id = $1;
945 # sending msg faild
946 if( $error ) {
947 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
948 my $sql_statement = "UPDATE $job_queue_tn ".
949 "SET status='error', result='can not deliver msg, please consult log file' ".
950 "WHERE id='$jobdb_id'";
951 my $res = $job_db->update_dbentry($sql_statement);
952 }
954 # sending msg was successful
955 } else {
956 my $sql_statement = "UPDATE $job_queue_tn ".
957 "SET status='done' ".
958 "WHERE id='$jobdb_id' AND status='processed'";
959 my $res = $job_db->update_dbentry($sql_statement);
960 }
961 }
962 }
964 sub _start {
965 my ($kernel) = $_[KERNEL];
966 &trigger_db_loop($kernel);
967 $global_kernel = $kernel;
968 $kernel->yield('create_fai_server_db', $fai_server_tn );
969 $kernel->yield('create_fai_release_db', $fai_release_tn );
970 $kernel->sig(USR1 => "sig_handler");
971 }
973 sub sig_handler {
974 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
975 daemon_log("0 INFO got signal '$signal'", 1);
976 $kernel->sig_handled();
977 return;
978 }
980 sub next_task {
981 my ($session, $heap) = @_[SESSION, HEAP];
983 while ( keys( %{ $heap->{task} } ) < $max_children ) {
984 my $next_task = shift @tasks;
985 last unless defined $next_task;
987 my $task = POE::Wheel::Run->new(
988 Program => sub { process_task($session, $heap, $next_task) },
989 StdioFilter => POE::Filter::Reference->new(),
990 StdoutEvent => "task_result",
991 StderrEvent => "task_debug",
992 CloseEvent => "task_done",
993 );
995 $heap->{task}->{ $task->ID } = $task;
996 }
997 }
999 sub handle_task_result {
1000 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1001 my $client_answer = $result->{'answer'};
1002 if( $client_answer =~ s/session_id=(\d+)$// ) {
1003 my $session_id = $1;
1004 if( defined $session_id ) {
1005 my $session_reference = $kernel->ID_id_to_session($session_id);
1006 if( defined $session_reference ) {
1007 $heap = $session_reference->get_heap();
1008 }
1009 }
1011 if(exists $heap->{'client'}) {
1012 $heap->{'client'}->put($client_answer);
1013 }
1014 }
1015 $kernel->sig(CHLD => "child_reap");
1016 }
1018 sub handle_task_debug {
1019 my $result = $_[ARG0];
1020 print STDERR "$result\n";
1021 }
1023 sub handle_task_done {
1024 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1025 delete $heap->{task}->{$task_id};
1026 $kernel->yield("next_task");
1027 }
1029 sub process_task {
1030 no strict "refs";
1031 my ($session, $heap, $input) = @_;
1032 my $session_id = $session->ID;
1033 my ($msg, $msg_hash, $module);
1034 my $error = 0;
1035 my $answer_l;
1036 my ($answer_header, @answer_target_l, $answer_source);
1037 my $client_answer = "";
1039 daemon_log("", 5);
1040 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1041 daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1043 ####################
1044 # check incoming msg
1045 # msg is from a new client or gosa
1046 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1047 # msg is from a gosa-si-server or gosa-si-bus
1048 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1049 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1050 }
1051 # msg is from a gosa-si-client
1052 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1054 }
1055 # an error occurred
1056 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1058 # could not understand a msg from its server the client cause a re-registering process
1059 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1060 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1061 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1062 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1063 my $host_name = $hit->{'hostname'};
1064 my $host_key = $hit->{'hostkey'};
1065 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1066 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1067 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1068 }
1069 $error++;
1070 }
1072 ######################
1073 # process incoming msg
1074 if( $error == 0) {
1075 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1076 "' from '".$heap->{'remote_ip'}."'", 5);
1077 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1078 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1080 if ( 0 < @{$answer_l} ) {
1081 my $answer_str = join("\n", @{$answer_l});
1082 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1083 }
1084 }
1085 if( !$answer_l ) { $error++ };
1087 ########
1088 # answer
1089 if( $error == 0 ) {
1091 foreach my $answer ( @{$answer_l} ) {
1092 # for each answer in answer list
1094 # check outgoing msg to xml validity
1095 my $answer_hash = &check_outgoing_xml_validity($answer);
1096 if( not defined $answer_hash ) {
1097 next;
1098 }
1100 $answer_header = @{$answer_hash->{'header'}}[0];
1101 @answer_target_l = @{$answer_hash->{'target'}};
1102 $answer_source = @{$answer_hash->{'source'}}[0];
1104 # deliver msg to all targets
1105 foreach my $answer_target ( @answer_target_l ) {
1107 # targets of msg are all gosa-si-clients in known_clients_db
1108 if( $answer_target eq "*" ) {
1109 # answer is for all clients
1110 my $sql_statement= "SELECT * FROM known_clients";
1111 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1112 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1113 my $host_name = $hit->{hostname};
1114 my $host_key = $hit->{hostkey};
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 # targets of msg are all gosa-si-server in known_server_db
1121 elsif( $answer_target eq "KNOWN_SERVER" ) {
1122 # answer is for all server in known_server
1123 my $sql_statement= "SELECT * FROM known_server";
1124 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1125 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1126 my $host_name = $hit->{hostname};
1127 my $host_key = $hit->{hostkey};
1128 $answer =~ s/KNOWN_SERVER/$host_name/g;
1129 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1130 &update_jobdb_status_for_send_msgs($answer, $error);
1131 }
1132 }
1134 # target of msg is GOsa
1135 elsif( $answer_target eq "GOSA" ) {
1136 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1137 my $add_on = "";
1138 if( defined $session_id ) {
1139 $add_on = ".session_id=$session_id";
1140 }
1141 # answer is for GOSA and has to returned to connected client
1142 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1143 $client_answer = $gosa_answer.$add_on;
1144 }
1146 # target of msg is job queue at this host
1147 elsif( $answer_target eq "JOBDB") {
1148 $answer =~ /<header>(\S+)<\/header>/;
1149 my $header;
1150 if( defined $1 ) { $header = $1; }
1151 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1152 &update_jobdb_status_for_send_msgs($answer, $error);
1153 }
1155 # target of msg is a mac address
1156 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 ) {
1157 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1158 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1159 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1160 my $found_ip_flag = 0;
1161 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1162 my $host_name = $hit->{hostname};
1163 my $host_key = $hit->{hostkey};
1164 $answer =~ s/$answer_target/$host_name/g;
1165 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1166 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1167 &update_jobdb_status_for_send_msgs($answer, $error);
1168 $found_ip_flag++ ;
1169 }
1170 if( $found_ip_flag == 0) {
1171 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1172 if( $bus_activ eq "true" ) {
1173 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1174 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1175 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1176 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1177 my $bus_address = $hit->{hostname};
1178 my $bus_key = $hit->{hostkey};
1179 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1180 &update_jobdb_status_for_send_msgs($answer, $error);
1181 last;
1182 }
1183 }
1185 }
1187 # answer is for one specific host
1188 } else {
1189 # get encrypt_key
1190 my $encrypt_key = &get_encrypt_key($answer_target);
1191 if( not defined $encrypt_key ) {
1192 # unknown target, forward msg to bus
1193 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1194 if( $bus_activ eq "true" ) {
1195 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1196 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1197 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1198 my $res_length = keys( %{$query_res} );
1199 if( $res_length == 0 ){
1200 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1201 "no bus found in known_server", 3);
1202 }
1203 else {
1204 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1205 my $bus_key = $hit->{hostkey};
1206 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1207 &update_jobdb_status_for_send_msgs($answer, $error);
1208 }
1209 }
1210 }
1211 next;
1212 }
1213 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1214 &update_jobdb_status_for_send_msgs($answer, $error);
1215 }
1216 }
1217 }
1218 }
1220 my $filter = POE::Filter::Reference->new();
1221 my %result = (
1222 status => "seems ok to me",
1223 answer => $client_answer,
1224 );
1226 my $output = $filter->put( [ \%result ] );
1227 print @$output;
1230 }
1233 sub trigger_db_loop {
1234 my ($kernel) = @_ ;
1235 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1236 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1237 }
1239 sub watch_for_done_jobs {
1240 my ($kernel,$heap) = @_[KERNEL, HEAP];
1242 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1243 " WHERE status='done'";
1244 my $res = $job_db->select_dbentry( $sql_statement );
1246 while( my ($id, $hit) = each %{$res} ) {
1247 my $jobdb_id = $hit->{id};
1248 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1249 my $res = $job_db->del_dbentry($sql_statement);
1250 }
1252 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1253 }
1255 sub watch_for_new_jobs {
1256 my ($kernel,$heap) = @_[KERNEL, HEAP];
1258 # check gosa job queue for jobs with executable timestamp
1259 my $timestamp = &get_time();
1260 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1261 my $res = $job_db->exec_statement( $sql_statement );
1263 # Merge all new jobs that would do the same actions
1264 my @drops;
1265 my $hits;
1266 foreach my $hit (reverse @{$res} ) {
1267 my $macaddress= lc @{$hit}[8];
1268 my $headertag= @{$hit}[5];
1269 if(defined($hits->{$macaddress}->{$headertag})) {
1270 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1271 }
1272 $hits->{$macaddress}->{$headertag}= $hit;
1273 }
1275 # Delete new jobs with a matching job in state 'processing'
1276 foreach my $macaddress (keys %{$hits}) {
1277 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1278 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1279 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1280 my $res = $job_db->exec_statement( $sql_statement );
1281 foreach my $hit (@{$res}) {
1282 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1283 }
1284 }
1285 }
1287 # Commit deletion
1288 $job_db->exec_statementlist(\@drops);
1290 # Look for new jobs that could be executed
1291 foreach my $macaddress (keys %{$hits}) {
1293 # Look if there is an executing job
1294 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1295 my $res = $job_db->exec_statement( $sql_statement );
1297 # Skip new jobs for host if there is a processing job
1298 if(defined($res) and defined @{$res}[0]) {
1299 next;
1300 }
1302 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1303 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1304 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1306 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1307 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1308 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1310 # expect macaddress is unique!!!!!!
1311 my $target = $res_hash->{1}->{hostname};
1313 # change header
1314 $job_msg =~ s/<header>job_/<header>gosa_/;
1316 # add sqlite_id
1317 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1319 $job_msg =~ /<header>(\S+)<\/header>/;
1320 my $header = $1 ;
1321 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1323 # update status in job queue to 'processing'
1324 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1325 my $res = $job_db->update_dbentry($sql_statement);
1327 # We don't want parallel processing
1328 last;
1329 }
1330 }
1332 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1333 }
1336 sub get_ldap_handle {
1337 my ($session_id) = @_;
1338 my $heap;
1339 my $ldap_handle;
1341 if (not defined $session_id ) { $session_id = 0 };
1343 if ($session_id == 0) {
1344 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1345 $ldap_handle = Net::LDAP->new( $ldap_uri );
1346 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1348 } else {
1349 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1350 if( defined $session_reference ) {
1351 $heap = $session_reference->get_heap();
1352 }
1354 if (not defined $heap) {
1355 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1356 return;
1357 }
1359 # TODO: This "if" is nonsense, because it doesn't prove that the
1360 # used handle is still valid - or if we've to reconnect...
1361 #if (not exists $heap->{ldap_handle}) {
1362 $ldap_handle = Net::LDAP->new( $ldap_uri );
1363 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1364 $heap->{ldap_handle} = $ldap_handle;
1365 #}
1366 }
1367 return $ldap_handle;
1368 }
1371 sub change_fai_state {
1372 my ($st, $targets, $session_id) = @_;
1373 $session_id = 0 if not defined $session_id;
1374 # Set FAI state to localboot
1375 my %mapActions= (
1376 reboot => '',
1377 update => 'softupdate',
1378 localboot => 'localboot',
1379 reinstall => 'install',
1380 rescan => '',
1381 wake => '',
1382 memcheck => 'memcheck',
1383 sysinfo => 'sysinfo',
1384 install => 'install',
1385 );
1387 # Return if this is unknown
1388 if (!exists $mapActions{ $st }){
1389 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1390 return;
1391 }
1393 my $state= $mapActions{ $st };
1395 my $ldap_handle = &get_ldap_handle($session_id);
1396 if( defined($ldap_handle) ) {
1398 # Build search filter for hosts
1399 my $search= "(&(objectClass=GOhard)";
1400 foreach (@{$targets}){
1401 $search.= "(macAddress=$_)";
1402 }
1403 $search.= ")";
1405 # If there's any host inside of the search string, procress them
1406 if (!($search =~ /macAddress/)){
1407 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1408 return;
1409 }
1411 # Perform search for Unit Tag
1412 my $mesg = $ldap_handle->search(
1413 base => $ldap_base,
1414 scope => 'sub',
1415 attrs => ['dn', 'FAIstate', 'objectClass'],
1416 filter => "$search"
1417 );
1419 if ($mesg->count) {
1420 my @entries = $mesg->entries;
1421 foreach my $entry (@entries) {
1422 # Only modify entry if it is not set to '$state'
1423 if ($entry->get_value("FAIstate") ne "$state"){
1424 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1425 my $result;
1426 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1427 if (exists $tmp{'FAIobject'}){
1428 if ($state eq ''){
1429 $result= $ldap_handle->modify($entry->dn, changes => [
1430 delete => [ FAIstate => [] ] ]);
1431 } else {
1432 $result= $ldap_handle->modify($entry->dn, changes => [
1433 replace => [ FAIstate => $state ] ]);
1434 }
1435 } elsif ($state ne ''){
1436 $result= $ldap_handle->modify($entry->dn, changes => [
1437 add => [ objectClass => 'FAIobject' ],
1438 add => [ FAIstate => $state ] ]);
1439 }
1441 # Errors?
1442 if ($result->code){
1443 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1444 }
1445 } else {
1446 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1447 }
1448 }
1449 }
1450 # if no ldap handle defined
1451 } else {
1452 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1453 }
1455 }
1458 sub change_goto_state {
1459 my ($st, $targets, $session_id) = @_;
1460 $session_id = 0 if not defined $session_id;
1462 # Switch on or off?
1463 my $state= $st eq 'active' ? 'active': 'locked';
1465 my $ldap_handle = &get_ldap_handle($session_id);
1466 if( defined($ldap_handle) ) {
1468 # Build search filter for hosts
1469 my $search= "(&(objectClass=GOhard)";
1470 foreach (@{$targets}){
1471 $search.= "(macAddress=$_)";
1472 }
1473 $search.= ")";
1475 # If there's any host inside of the search string, procress them
1476 if (!($search =~ /macAddress/)){
1477 return;
1478 }
1480 # Perform search for Unit Tag
1481 my $mesg = $ldap_handle->search(
1482 base => $ldap_base,
1483 scope => 'sub',
1484 attrs => ['dn', 'gotoMode'],
1485 filter => "$search"
1486 );
1488 if ($mesg->count) {
1489 my @entries = $mesg->entries;
1490 foreach my $entry (@entries) {
1492 # Only modify entry if it is not set to '$state'
1493 if ($entry->get_value("gotoMode") ne $state){
1495 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1496 my $result;
1497 $result= $ldap_handle->modify($entry->dn, changes => [
1498 replace => [ gotoMode => $state ] ]);
1500 # Errors?
1501 if ($result->code){
1502 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1503 }
1505 }
1506 }
1507 }
1509 }
1510 }
1513 sub create_fai_server_db {
1514 my ($table_name, $kernel) = @_;
1515 my $result;
1516 my $ldap_handle = &get_ldap_handle();
1517 if(defined($ldap_handle)) {
1518 daemon_log("INFO: create_fai_server_db: start", 5);
1519 my $mesg= $ldap_handle->search(
1520 base => $ldap_base,
1521 scope => 'sub',
1522 attrs => ['FAIrepository', 'gosaUnitTag'],
1523 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1524 );
1525 if($mesg->{'resultCode'} == 0 &&
1526 $mesg->count != 0) {
1527 foreach my $entry (@{$mesg->{entries}}) {
1528 if($entry->exists('FAIrepository')) {
1529 # Add an entry for each Repository configured for server
1530 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1531 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1532 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1533 $result= $fai_server_db->add_dbentry( {
1534 table => $table_name,
1535 primkey => ['server', 'release', 'tag'],
1536 server => $tmp_url,
1537 release => $tmp_release,
1538 sections => $tmp_sections,
1539 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1540 } );
1541 }
1542 }
1543 }
1544 }
1545 daemon_log("INFO: create_fai_server_db: finished", 5);
1547 # TODO: Find a way to post the 'create_packages_list_db' event
1548 &create_packages_list_db($ldap_handle);
1549 }
1551 $ldap_handle->disconnect;
1552 return $result;
1553 }
1555 sub run_create_fai_server_db {
1556 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1557 my $task = POE::Wheel::Run->new(
1558 Program => sub { &create_fai_server_db($table_name,$kernel) },
1559 StdoutEvent => "session_run_result",
1560 StderrEvent => "session_run_debug",
1561 CloseEvent => "session_run_done",
1562 );
1564 $heap->{task}->{ $task->ID } = $task;
1565 return;
1566 }
1569 sub create_fai_release_db {
1570 my ($table_name) = @_;
1571 my $result;
1573 my $ldap_handle = &get_ldap_handle();
1574 if(defined($ldap_handle)) {
1575 daemon_log("INFO: create_fai_release_db: start",5);
1576 my $mesg= $ldap_handle->search(
1577 base => $ldap_base,
1578 scope => 'sub',
1579 attrs => [],
1580 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1581 );
1582 if($mesg->{'resultCode'} == 0 &&
1583 $mesg->count != 0) {
1584 # Walk through all possible FAI container ou's
1585 my @sql_list;
1586 my $timestamp= &get_time();
1587 foreach my $ou (@{$mesg->{entries}}) {
1588 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1589 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1590 my @tmp_array=get_fai_release_entries($tmp_classes);
1591 if(@tmp_array) {
1592 foreach my $entry (@tmp_array) {
1593 if(defined($entry) && ref($entry) eq 'HASH') {
1594 my $sql=
1595 "INSERT INTO $table_name "
1596 ."(timestamp, release, class, type, state) VALUES ("
1597 .$timestamp.","
1598 ."'".$entry->{'release'}."',"
1599 ."'".$entry->{'class'}."',"
1600 ."'".$entry->{'type'}."',"
1601 ."'".$entry->{'state'}."')";
1602 push @sql_list, $sql;
1603 }
1604 }
1605 }
1606 }
1607 }
1608 daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1609 if(@sql_list) {
1610 unshift @sql_list, "DELETE FROM $table_name";
1611 $fai_server_db->exec_statementlist(\@sql_list);
1612 }
1613 daemon_log("DEBUG: Done with inserting",6);
1614 }
1615 daemon_log("INFO: create_fai_release_db: finished",5);
1616 }
1617 $ldap_handle->disconnect;
1618 return $result;
1619 }
1620 sub run_create_fai_release_db {
1621 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1622 my $task = POE::Wheel::Run->new(
1623 Program => sub { &create_fai_release_db($table_name) },
1624 StdoutEvent => "session_run_result",
1625 StderrEvent => "session_run_debug",
1626 CloseEvent => "session_run_done",
1627 );
1629 $heap->{task}->{ $task->ID } = $task;
1630 return;
1631 }
1633 sub get_fai_types {
1634 my $tmp_classes = shift || return undef;
1635 my @result;
1637 foreach my $type(keys %{$tmp_classes}) {
1638 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1639 my $entry = {
1640 type => $type,
1641 state => $tmp_classes->{$type}[0],
1642 };
1643 push @result, $entry;
1644 }
1645 }
1647 return @result;
1648 }
1650 sub get_fai_state {
1651 my $result = "";
1652 my $tmp_classes = shift || return $result;
1654 foreach my $type(keys %{$tmp_classes}) {
1655 if(defined($tmp_classes->{$type}[0])) {
1656 $result = $tmp_classes->{$type}[0];
1658 # State is equal for all types in class
1659 last;
1660 }
1661 }
1663 return $result;
1664 }
1666 sub resolve_fai_classes {
1667 my ($fai_base, $ldap_handle) = @_;
1668 my $result;
1669 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1670 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1671 my $fai_classes;
1673 daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1674 my $mesg= $ldap_handle->search(
1675 base => $fai_base,
1676 scope => 'sub',
1677 attrs => ['cn','objectClass','FAIstate'],
1678 filter => $fai_filter,
1679 );
1680 daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1682 if($mesg->{'resultCode'} == 0 &&
1683 $mesg->count != 0) {
1684 foreach my $entry (@{$mesg->{entries}}) {
1685 if($entry->exists('cn')) {
1686 my $tmp_dn= $entry->dn();
1688 # Skip classname and ou dn parts for class
1689 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1691 # Skip classes without releases
1692 if((!defined($tmp_release)) || length($tmp_release)==0) {
1693 next;
1694 }
1696 my $tmp_cn= $entry->get_value('cn');
1697 my $tmp_state= $entry->get_value('FAIstate');
1699 my $tmp_type;
1700 # Get FAI type
1701 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1702 if(grep $_ eq $oclass, @possible_fai_classes) {
1703 $tmp_type= $oclass;
1704 last;
1705 }
1706 }
1708 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1709 # A Subrelease
1710 my @sub_releases = split(/,/, $tmp_release);
1712 # Walk through subreleases and build hash tree
1713 my $hash;
1714 while(my $tmp_sub_release = pop @sub_releases) {
1715 $hash .= "\{'$tmp_sub_release'\}->";
1716 }
1717 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1718 } else {
1719 # A branch, no subrelease
1720 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1721 }
1722 } elsif (!$entry->exists('cn')) {
1723 my $tmp_dn= $entry->dn();
1724 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1726 # Skip classes without releases
1727 if((!defined($tmp_release)) || length($tmp_release)==0) {
1728 next;
1729 }
1731 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1732 # A Subrelease
1733 my @sub_releases= split(/,/, $tmp_release);
1735 # Walk through subreleases and build hash tree
1736 my $hash;
1737 while(my $tmp_sub_release = pop @sub_releases) {
1738 $hash .= "\{'$tmp_sub_release'\}->";
1739 }
1740 # Remove the last two characters
1741 chop($hash);
1742 chop($hash);
1744 eval('$fai_classes->'.$hash.'= {}');
1745 } else {
1746 # A branch, no subrelease
1747 if(!exists($fai_classes->{$tmp_release})) {
1748 $fai_classes->{$tmp_release} = {};
1749 }
1750 }
1751 }
1752 }
1754 # The hash is complete, now we can honor the copy-on-write based missing entries
1755 foreach my $release (keys %$fai_classes) {
1756 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1757 }
1758 }
1759 return $result;
1760 }
1762 sub apply_fai_inheritance {
1763 my $fai_classes = shift || return {};
1764 my $tmp_classes;
1766 # Get the classes from the branch
1767 foreach my $class (keys %{$fai_classes}) {
1768 # Skip subreleases
1769 if($class =~ /^ou=.*$/) {
1770 next;
1771 } else {
1772 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1773 }
1774 }
1776 # Apply to each subrelease
1777 foreach my $subrelease (keys %{$fai_classes}) {
1778 if($subrelease =~ /ou=/) {
1779 foreach my $tmp_class (keys %{$tmp_classes}) {
1780 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1781 $fai_classes->{$subrelease}->{$tmp_class} =
1782 deep_copy($tmp_classes->{$tmp_class});
1783 } else {
1784 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1785 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1786 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1787 deep_copy($tmp_classes->{$tmp_class}->{$type});
1788 }
1789 }
1790 }
1791 }
1792 }
1793 }
1795 # Find subreleases in deeper levels
1796 foreach my $subrelease (keys %{$fai_classes}) {
1797 if($subrelease =~ /ou=/) {
1798 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1799 if($subsubrelease =~ /ou=/) {
1800 apply_fai_inheritance($fai_classes->{$subrelease});
1801 }
1802 }
1803 }
1804 }
1806 return $fai_classes;
1807 }
1809 sub get_fai_release_entries {
1810 my $tmp_classes = shift || return;
1811 my $parent = shift || "";
1812 my @result = shift || ();
1814 foreach my $entry (keys %{$tmp_classes}) {
1815 if(defined($entry)) {
1816 if($entry =~ /^ou=.*$/) {
1817 my $release_name = $entry;
1818 $release_name =~ s/ou=//g;
1819 if(length($parent)>0) {
1820 $release_name = $parent."/".$release_name;
1821 }
1822 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1823 foreach my $bufentry(@bufentries) {
1824 push @result, $bufentry;
1825 }
1826 } else {
1827 my @types = get_fai_types($tmp_classes->{$entry});
1828 foreach my $type (@types) {
1829 push @result,
1830 {
1831 'class' => $entry,
1832 'type' => $type->{'type'},
1833 'release' => $parent,
1834 'state' => $type->{'state'},
1835 };
1836 }
1837 }
1838 }
1839 }
1841 return @result;
1842 }
1844 sub deep_copy {
1845 my $this = shift;
1846 if (not ref $this) {
1847 $this;
1848 } elsif (ref $this eq "ARRAY") {
1849 [map deep_copy($_), @$this];
1850 } elsif (ref $this eq "HASH") {
1851 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1852 } else { die "what type is $_?" }
1853 }
1856 sub session_run_result {
1857 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1858 $kernel->sig(CHLD => "child_reap");
1859 }
1861 sub session_run_debug {
1862 my $result = $_[ARG0];
1863 print STDERR "$result\n";
1864 }
1866 sub session_run_done {
1867 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1868 delete $heap->{task}->{$task_id};
1869 }
1871 sub create_sources_list {
1872 my ($ldap_handle) = @_;
1873 my $result="/tmp/gosa_si_tmp_sources_list";
1875 # Remove old file
1876 if(stat($result)) {
1877 unlink($result);
1878 }
1880 my $fh;
1881 open($fh, ">$result") or return undef;
1882 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1883 my $mesg=$ldap_handle->search(
1884 base => $ldap_server_dn,
1885 scope => 'base',
1886 attrs => 'FAIrepository',
1887 filter => 'objectClass=FAIrepositoryServer'
1888 );
1889 if($mesg->count) {
1890 foreach my $entry(@{$mesg->{'entries'}}) {
1891 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1892 my $line = "deb $server $release";
1893 $sections =~ s/,/ /g;
1894 $line.= " $sections";
1895 print $fh $line."\n";
1896 }
1897 }
1898 }
1899 close($fh);
1901 return $result;
1902 }
1904 sub create_packages_list_db {
1905 my ($ldap_handle, $sources_file) = @_ ;
1907 if (not defined $ldap_handle) {
1908 daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1909 return;
1910 }
1911 if (not defined $sources_file) {
1912 $sources_file = &create_sources_list($ldap_handle);
1913 }
1915 my $line;
1916 daemon_log("INFO: create_packages_list_db: start", 5);
1918 open(CONFIG, "<$sources_file") or do {
1919 daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1920 return;
1921 };
1923 # Read lines
1924 while ($line = <CONFIG>){
1925 # Unify
1926 chop($line);
1927 $line =~ s/^\s+//;
1928 $line =~ s/^\s+/ /;
1930 # Strip comments
1931 $line =~ s/#.*$//g;
1933 # Skip empty lines
1934 if ($line =~ /^\s*$/){
1935 next;
1936 }
1938 # Interpret deb line
1939 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1940 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1941 my $section;
1942 foreach $section (split(' ', $sections)){
1943 &parse_package_info( $baseurl, $dist, $section );
1944 }
1945 }
1946 }
1948 close (CONFIG);
1950 daemon_log("INFO: create_packages_list_db: finished", 5);
1951 return;
1952 }
1954 sub run_create_packages_list_db {
1955 my ($session, $heap) = @_[SESSION, HEAP];
1956 my $task = POE::Wheel::Run->new(
1957 Program => sub {&create_packages_list_db},
1958 StdoutEvent => "session_run_result",
1959 StderrEvent => "session_run_debug",
1960 CloseEvent => "session_run_done",
1961 );
1962 $heap->{task}->{ $task->ID } = $task;
1963 }
1965 sub parse_package_info {
1966 my ($baseurl, $dist, $section)= @_;
1967 my ($package);
1969 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1970 $repo_dirs{ "${repo_path}/pool" } = 1;
1972 foreach $package ("Packages.gz"){
1973 daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1974 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1975 parse_package( "$outdir/$dist/$section", $dist, $path );
1976 }
1977 find(\&cleanup_and_extract, keys( %repo_dirs ) );
1978 }
1980 sub get_package {
1981 my ($url, $dest)= @_;
1983 my $tpath = dirname($dest);
1984 -d "$tpath" || mkpath "$tpath";
1986 # This is ugly, but I've no time to take a look at "how it works in perl"
1987 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1988 system("gunzip -cd '$dest' > '$dest.in'");
1989 unlink($dest);
1990 } else {
1991 daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1992 }
1993 return 0;
1994 }
1996 sub parse_package {
1997 my ($path, $dist, $srv_path)= @_;
1998 my ($package, $version, $section, $description);
1999 my @sql_list;
2000 my $PACKAGES;
2002 if(not stat("$path.in")) {
2003 daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2004 return;
2005 }
2007 open($PACKAGES, "<$path.in");
2008 if(not defined($PACKAGES)) {
2009 daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1);
2010 return;
2011 }
2013 # Read lines
2014 while (<$PACKAGES>){
2015 my $line = $_;
2016 # Unify
2017 chop($line);
2019 # Use empty lines as a trigger
2020 if ($line =~ /^\s*$/){
2021 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2022 push(@sql_list, $sql);
2023 $package = "none";
2024 $version = "none";
2025 $section = "none";
2026 $description = "none";
2027 next;
2028 }
2030 # Trigger for package name
2031 if ($line =~ /^Package:\s/){
2032 ($package)= ($line =~ /^Package: (.*)$/);
2033 next;
2034 }
2036 # Trigger for version
2037 if ($line =~ /^Version:\s/){
2038 ($version)= ($line =~ /^Version: (.*)$/);
2039 next;
2040 }
2042 # Trigger for description
2043 if ($line =~ /^Description:\s/){
2044 ($description)= ($line =~ /^Description: (.*)$/);
2045 next;
2046 }
2048 # Trigger for section
2049 if ($line =~ /^Section:\s/){
2050 ($section)= ($line =~ /^Section: (.*)$/);
2051 next;
2052 }
2054 # Trigger for filename
2055 if ($line =~ /^Filename:\s/){
2056 my ($filename) = ($line =~ /^Filename: (.*)$/);
2057 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2058 next;
2059 }
2060 }
2062 close( $PACKAGES );
2063 unlink( "$path.in" );
2065 $packages_list_db->exec_statementlist(\@sql_list);
2066 }
2068 sub store_fileinfo {
2069 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2071 my %fileinfo = (
2072 'package' => $package,
2073 'dist' => $dist,
2074 'version' => $vers,
2075 );
2077 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2078 }
2080 sub cleanup_and_extract {
2081 my $fileinfo = $repo_files{ $File::Find::name };
2083 if( defined $fileinfo ) {
2085 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2086 my $sql;
2087 my $package = $fileinfo->{ 'package' };
2088 my $newver = $fileinfo->{ 'version' };
2090 mkpath($dir);
2091 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2093 if( -f "$dir/DEBIAN/templates" ) {
2095 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2097 my $tmpl= "";
2098 {
2099 local $/=undef;
2100 open FILE, "$dir/DEBIAN/templates";
2101 $tmpl = &encode_base64(<FILE>);
2102 close FILE;
2103 }
2104 rmtree("$dir/DEBIAN/templates");
2106 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2108 } else {
2109 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2110 }
2112 my $res= $main::packages_list_db->update_dbentry($sql);
2113 }
2114 }
2117 #==== MAIN = main ==============================================================
2118 # parse commandline options
2119 Getopt::Long::Configure( "bundling" );
2120 GetOptions("h|help" => \&usage,
2121 "c|config=s" => \$cfg_file,
2122 "f|foreground" => \$foreground,
2123 "v|verbose+" => \$verbose,
2124 "no-bus+" => \$no_bus,
2125 "no-arp+" => \$no_arp,
2126 );
2128 # read and set config parameters
2129 &check_cmdline_param ;
2130 &read_configfile;
2131 &check_pid;
2133 $SIG{CHLD} = 'IGNORE';
2135 # forward error messages to logfile
2136 if( ! $foreground ) {
2137 open( STDIN, '+>/dev/null' );
2138 open( STDOUT, '+>&STDIN' );
2139 open( STDERR, '+>&STDIN' );
2140 }
2142 # Just fork, if we are not in foreground mode
2143 if( ! $foreground ) {
2144 chdir '/' or die "Can't chdir to /: $!";
2145 $pid = fork;
2146 setsid or die "Can't start a new session: $!";
2147 umask 0;
2148 } else {
2149 $pid = $$;
2150 }
2152 # Do something useful - put our PID into the pid_file
2153 if( 0 != $pid ) {
2154 open( LOCK_FILE, ">$pid_file" );
2155 print LOCK_FILE "$pid\n";
2156 close( LOCK_FILE );
2157 if( !$foreground ) {
2158 exit( 0 )
2159 };
2160 }
2162 daemon_log(" ", 1);
2163 daemon_log("$0 started!", 1);
2165 if ($no_bus > 0) {
2166 $bus_activ = "false"
2167 }
2169 # connect to gosa-si job queue
2170 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2171 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2173 # connect to known_clients_db
2174 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2175 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2177 # connect to known_server_db
2178 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2179 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2181 # connect to login_usr_db
2182 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2183 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2185 # connect to fai_server_db and fai_release_db
2186 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2187 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2188 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2190 # connect to packages_list_db
2191 unlink($packages_list_file_name);
2192 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2193 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2195 # connect to messaging_db
2196 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2197 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2200 # create xml object used for en/decrypting
2201 $xml = new XML::Simple();
2203 # create socket for incoming xml messages
2205 POE::Component::Server::TCP->new(
2206 Port => $server_port,
2207 ClientInput => sub {
2208 my ($kernel, $input) = @_[KERNEL, ARG0];
2209 push(@tasks, $input);
2210 $kernel->yield("next_task");
2211 },
2212 InlineStates => {
2213 next_task => \&next_task,
2214 task_result => \&handle_task_result,
2215 task_done => \&handle_task_done,
2216 task_debug => \&handle_task_debug,
2217 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2218 }
2219 );
2221 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2223 # create session for repeatedly checking the job queue for jobs
2224 POE::Session->create(
2225 inline_states => {
2226 _start => \&_start,
2227 sig_handler => \&sig_handler,
2228 watch_for_new_jobs => \&watch_for_new_jobs,
2229 watch_for_done_jobs => \&watch_for_done_jobs,
2230 create_packages_list_db => \&run_create_packages_list_db,
2231 create_fai_server_db => \&run_create_fai_server_db,
2232 create_fai_release_db => \&run_create_fai_release_db,
2233 session_run_result => \&session_run_result,
2234 session_run_debug => \&session_run_debug,
2235 session_run_done => \&session_run_done,
2236 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2237 }
2238 );
2241 # import all modules
2242 &import_modules;
2244 # check wether all modules are gosa-si valid passwd check
2246 POE::Kernel->run();
2247 exit;