9d48dc4ff217c3804045b8075108a3476f52f743
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) && ( (-s $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 $kernel->sig(USR2 => "create_packages_list_db");
972 }
974 sub sig_handler {
975 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
976 daemon_log("0 INFO got signal '$signal'", 1);
977 $kernel->sig_handled();
978 return;
979 }
981 sub next_task {
982 my ($session, $heap) = @_[SESSION, HEAP];
984 while ( keys( %{ $heap->{task} } ) < $max_children ) {
985 my $next_task = shift @tasks;
986 last unless defined $next_task;
988 my $task = POE::Wheel::Run->new(
989 Program => sub { process_task($session, $heap, $next_task) },
990 StdioFilter => POE::Filter::Reference->new(),
991 StdoutEvent => "task_result",
992 StderrEvent => "task_debug",
993 CloseEvent => "task_done",
994 );
996 $heap->{task}->{ $task->ID } = $task;
997 }
998 }
1000 sub handle_task_result {
1001 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1002 my $client_answer = $result->{'answer'};
1003 if( $client_answer =~ s/session_id=(\d+)$// ) {
1004 my $session_id = $1;
1005 if( defined $session_id ) {
1006 my $session_reference = $kernel->ID_id_to_session($session_id);
1007 if( defined $session_reference ) {
1008 $heap = $session_reference->get_heap();
1009 }
1010 }
1012 if(exists $heap->{'client'}) {
1013 $heap->{'client'}->put($client_answer);
1014 }
1015 }
1016 $kernel->sig(CHLD => "child_reap");
1017 }
1019 sub handle_task_debug {
1020 my $result = $_[ARG0];
1021 print STDERR "$result\n";
1022 }
1024 sub handle_task_done {
1025 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1026 delete $heap->{task}->{$task_id};
1027 $kernel->yield("next_task");
1028 }
1030 sub process_task {
1031 no strict "refs";
1032 my ($session, $heap, $input) = @_;
1033 my $session_id = $session->ID;
1034 my ($msg, $msg_hash, $module);
1035 my $error = 0;
1036 my $answer_l;
1037 my ($answer_header, @answer_target_l, $answer_source);
1038 my $client_answer = "";
1040 daemon_log("", 5);
1041 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1042 daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1044 ####################
1045 # check incoming msg
1046 # msg is from a new client or gosa
1047 ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1048 # msg is from a gosa-si-server or gosa-si-bus
1049 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1050 ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1051 }
1052 # msg is from a gosa-si-client
1053 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1054 ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1055 }
1056 # an error occurred
1057 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1058 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1059 # could not understand a msg from its server the client cause a re-registering process
1060 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);
1061 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1062 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1063 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1064 my $host_name = $hit->{'hostname'};
1065 my $host_key = $hit->{'hostkey'};
1066 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1067 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1068 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1069 }
1070 $error++;
1071 }
1073 ######################
1074 # process incoming msg
1075 if( $error == 0) {
1076 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1077 "' from '".$heap->{'remote_ip'}."'", 5);
1078 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1079 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1081 if ( 0 < @{$answer_l} ) {
1082 my $answer_str = join("\n", @{$answer_l});
1083 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1084 }
1085 }
1086 if( !$answer_l ) { $error++ };
1088 ########
1089 # answer
1090 if( $error == 0 ) {
1092 foreach my $answer ( @{$answer_l} ) {
1093 # for each answer in answer list
1095 # check outgoing msg to xml validity
1096 my $answer_hash = &check_outgoing_xml_validity($answer);
1097 if( not defined $answer_hash ) {
1098 next;
1099 }
1101 $answer_header = @{$answer_hash->{'header'}}[0];
1102 @answer_target_l = @{$answer_hash->{'target'}};
1103 $answer_source = @{$answer_hash->{'source'}}[0];
1105 # deliver msg to all targets
1106 foreach my $answer_target ( @answer_target_l ) {
1108 # targets of msg are all gosa-si-clients in known_clients_db
1109 if( $answer_target eq "*" ) {
1110 # answer is for all clients
1111 my $sql_statement= "SELECT * FROM known_clients";
1112 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1113 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1114 my $host_name = $hit->{hostname};
1115 my $host_key = $hit->{hostkey};
1116 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1117 &update_jobdb_status_for_send_msgs($answer, $error);
1118 }
1119 }
1121 # targets of msg are all gosa-si-server in known_server_db
1122 elsif( $answer_target eq "KNOWN_SERVER" ) {
1123 # answer is for all server in known_server
1124 my $sql_statement= "SELECT * FROM known_server";
1125 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1126 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1127 my $host_name = $hit->{hostname};
1128 my $host_key = $hit->{hostkey};
1129 $answer =~ s/KNOWN_SERVER/$host_name/g;
1130 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1131 &update_jobdb_status_for_send_msgs($answer, $error);
1132 }
1133 }
1135 # target of msg is GOsa
1136 elsif( $answer_target eq "GOSA" ) {
1137 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1138 my $add_on = "";
1139 if( defined $session_id ) {
1140 $add_on = ".session_id=$session_id";
1141 }
1142 # answer is for GOSA and has to returned to connected client
1143 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1144 $client_answer = $gosa_answer.$add_on;
1145 }
1147 # target of msg is job queue at this host
1148 elsif( $answer_target eq "JOBDB") {
1149 $answer =~ /<header>(\S+)<\/header>/;
1150 my $header;
1151 if( defined $1 ) { $header = $1; }
1152 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1153 &update_jobdb_status_for_send_msgs($answer, $error);
1154 }
1156 # target of msg is a mac address
1157 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 ) {
1158 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1159 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1160 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1161 my $found_ip_flag = 0;
1162 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1163 my $host_name = $hit->{hostname};
1164 my $host_key = $hit->{hostkey};
1165 $answer =~ s/$answer_target/$host_name/g;
1166 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1167 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1168 &update_jobdb_status_for_send_msgs($answer, $error);
1169 $found_ip_flag++ ;
1170 }
1171 if( $found_ip_flag == 0) {
1172 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1173 if( $bus_activ eq "true" ) {
1174 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1175 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1176 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1177 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1178 my $bus_address = $hit->{hostname};
1179 my $bus_key = $hit->{hostkey};
1180 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1181 &update_jobdb_status_for_send_msgs($answer, $error);
1182 last;
1183 }
1184 }
1186 }
1188 # answer is for one specific host
1189 } else {
1190 # get encrypt_key
1191 my $encrypt_key = &get_encrypt_key($answer_target);
1192 if( not defined $encrypt_key ) {
1193 # unknown target, forward msg to bus
1194 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1195 if( $bus_activ eq "true" ) {
1196 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1197 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1198 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1199 my $res_length = keys( %{$query_res} );
1200 if( $res_length == 0 ){
1201 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1202 "no bus found in known_server", 3);
1203 }
1204 else {
1205 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1206 my $bus_key = $hit->{hostkey};
1207 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1208 &update_jobdb_status_for_send_msgs($answer, $error);
1209 }
1210 }
1211 }
1212 next;
1213 }
1214 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1215 &update_jobdb_status_for_send_msgs($answer, $error);
1216 }
1217 }
1218 }
1219 }
1221 my $filter = POE::Filter::Reference->new();
1222 my %result = (
1223 status => "seems ok to me",
1224 answer => $client_answer,
1225 );
1227 my $output = $filter->put( [ \%result ] );
1228 print @$output;
1231 }
1234 sub trigger_db_loop {
1235 my ($kernel) = @_ ;
1236 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1237 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1238 }
1240 sub watch_for_done_jobs {
1241 my ($kernel,$heap) = @_[KERNEL, HEAP];
1243 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1244 " WHERE status='done'";
1245 my $res = $job_db->select_dbentry( $sql_statement );
1247 while( my ($id, $hit) = each %{$res} ) {
1248 my $jobdb_id = $hit->{id};
1249 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'";
1250 my $res = $job_db->del_dbentry($sql_statement);
1251 }
1253 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1254 }
1256 sub watch_for_new_jobs {
1257 my ($kernel,$heap) = @_[KERNEL, HEAP];
1259 # check gosa job queue for jobs with executable timestamp
1260 my $timestamp = &get_time();
1261 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER) + 120) < $timestamp ORDER BY timestamp";
1262 my $res = $job_db->exec_statement( $sql_statement );
1264 # Merge all new jobs that would do the same actions
1265 my @drops;
1266 my $hits;
1267 foreach my $hit (reverse @{$res} ) {
1268 my $macaddress= lc @{$hit}[8];
1269 my $headertag= @{$hit}[5];
1270 if(defined($hits->{$macaddress}->{$headertag})) {
1271 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$hits->{$macaddress}->{$headertag}[0]'";
1272 }
1273 $hits->{$macaddress}->{$headertag}= $hit;
1274 }
1276 # Delete new jobs with a matching job in state 'processing'
1277 foreach my $macaddress (keys %{$hits}) {
1278 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1279 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1280 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1281 my $res = $job_db->exec_statement( $sql_statement );
1282 foreach my $hit (@{$res}) {
1283 push @drops, "DELETE FROM $job_queue_tn WHERE id = '$jobdb_id'";
1284 }
1285 }
1286 }
1288 # Commit deletion
1289 $job_db->exec_statementlist(\@drops);
1291 # Look for new jobs that could be executed
1292 foreach my $macaddress (keys %{$hits}) {
1294 # Look if there is an executing job
1295 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1296 my $res = $job_db->exec_statement( $sql_statement );
1298 # Skip new jobs for host if there is a processing job
1299 if(defined($res) and defined @{$res}[0]) {
1300 next;
1301 }
1303 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1304 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1305 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1307 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1308 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1309 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1311 # expect macaddress is unique!!!!!!
1312 my $target = $res_hash->{1}->{hostname};
1314 # change header
1315 $job_msg =~ s/<header>job_/<header>gosa_/;
1317 # add sqlite_id
1318 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1320 $job_msg =~ /<header>(\S+)<\/header>/;
1321 my $header = $1 ;
1322 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1324 # update status in job queue to 'processing'
1325 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1326 my $res = $job_db->update_dbentry($sql_statement);
1328 # We don't want parallel processing
1329 last;
1330 }
1331 }
1333 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1334 }
1337 sub get_ldap_handle {
1338 my ($session_id) = @_;
1339 my $heap;
1340 my $ldap_handle;
1342 if (not defined $session_id ) { $session_id = 0 };
1344 if ($session_id == 0) {
1345 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1346 $ldap_handle = Net::LDAP->new( $ldap_uri );
1347 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1349 } else {
1350 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1351 if( defined $session_reference ) {
1352 $heap = $session_reference->get_heap();
1353 }
1355 if (not defined $heap) {
1356 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1357 return;
1358 }
1360 # TODO: This "if" is nonsense, because it doesn't prove that the
1361 # used handle is still valid - or if we've to reconnect...
1362 #if (not exists $heap->{ldap_handle}) {
1363 $ldap_handle = Net::LDAP->new( $ldap_uri );
1364 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1365 $heap->{ldap_handle} = $ldap_handle;
1366 #}
1367 }
1368 return $ldap_handle;
1369 }
1372 sub change_fai_state {
1373 my ($st, $targets, $session_id) = @_;
1374 $session_id = 0 if not defined $session_id;
1375 # Set FAI state to localboot
1376 my %mapActions= (
1377 reboot => '',
1378 update => 'softupdate',
1379 localboot => 'localboot',
1380 reinstall => 'install',
1381 rescan => '',
1382 wake => '',
1383 memcheck => 'memcheck',
1384 sysinfo => 'sysinfo',
1385 install => 'install',
1386 );
1388 # Return if this is unknown
1389 if (!exists $mapActions{ $st }){
1390 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1391 return;
1392 }
1394 my $state= $mapActions{ $st };
1396 my $ldap_handle = &get_ldap_handle($session_id);
1397 if( defined($ldap_handle) ) {
1399 # Build search filter for hosts
1400 my $search= "(&(objectClass=GOhard)";
1401 foreach (@{$targets}){
1402 $search.= "(macAddress=$_)";
1403 }
1404 $search.= ")";
1406 # If there's any host inside of the search string, procress them
1407 if (!($search =~ /macAddress/)){
1408 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1409 return;
1410 }
1412 # Perform search for Unit Tag
1413 my $mesg = $ldap_handle->search(
1414 base => $ldap_base,
1415 scope => 'sub',
1416 attrs => ['dn', 'FAIstate', 'objectClass'],
1417 filter => "$search"
1418 );
1420 if ($mesg->count) {
1421 my @entries = $mesg->entries;
1422 foreach my $entry (@entries) {
1423 # Only modify entry if it is not set to '$state'
1424 if ($entry->get_value("FAIstate") ne "$state"){
1425 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1426 my $result;
1427 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1428 if (exists $tmp{'FAIobject'}){
1429 if ($state eq ''){
1430 $result= $ldap_handle->modify($entry->dn, changes => [
1431 delete => [ FAIstate => [] ] ]);
1432 } else {
1433 $result= $ldap_handle->modify($entry->dn, changes => [
1434 replace => [ FAIstate => $state ] ]);
1435 }
1436 } elsif ($state ne ''){
1437 $result= $ldap_handle->modify($entry->dn, changes => [
1438 add => [ objectClass => 'FAIobject' ],
1439 add => [ FAIstate => $state ] ]);
1440 }
1442 # Errors?
1443 if ($result->code){
1444 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1445 }
1446 } else {
1447 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1448 }
1449 }
1450 }
1451 # if no ldap handle defined
1452 } else {
1453 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1454 }
1456 }
1459 sub change_goto_state {
1460 my ($st, $targets, $session_id) = @_;
1461 $session_id = 0 if not defined $session_id;
1463 # Switch on or off?
1464 my $state= $st eq 'active' ? 'active': 'locked';
1466 my $ldap_handle = &get_ldap_handle($session_id);
1467 if( defined($ldap_handle) ) {
1469 # Build search filter for hosts
1470 my $search= "(&(objectClass=GOhard)";
1471 foreach (@{$targets}){
1472 $search.= "(macAddress=$_)";
1473 }
1474 $search.= ")";
1476 # If there's any host inside of the search string, procress them
1477 if (!($search =~ /macAddress/)){
1478 return;
1479 }
1481 # Perform search for Unit Tag
1482 my $mesg = $ldap_handle->search(
1483 base => $ldap_base,
1484 scope => 'sub',
1485 attrs => ['dn', 'gotoMode'],
1486 filter => "$search"
1487 );
1489 if ($mesg->count) {
1490 my @entries = $mesg->entries;
1491 foreach my $entry (@entries) {
1493 # Only modify entry if it is not set to '$state'
1494 if ($entry->get_value("gotoMode") ne $state){
1496 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1497 my $result;
1498 $result= $ldap_handle->modify($entry->dn, changes => [
1499 replace => [ gotoMode => $state ] ]);
1501 # Errors?
1502 if ($result->code){
1503 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1504 }
1506 }
1507 }
1508 }
1510 }
1511 }
1514 sub create_fai_server_db {
1515 my ($table_name, $kernel) = @_;
1516 my $result;
1517 my $ldap_handle = &get_ldap_handle();
1518 if(defined($ldap_handle)) {
1519 daemon_log("INFO: create_fai_server_db: start", 5);
1520 my $mesg= $ldap_handle->search(
1521 base => $ldap_base,
1522 scope => 'sub',
1523 attrs => ['FAIrepository', 'gosaUnitTag'],
1524 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1525 );
1526 if($mesg->{'resultCode'} == 0 &&
1527 $mesg->count != 0) {
1528 foreach my $entry (@{$mesg->{entries}}) {
1529 if($entry->exists('FAIrepository')) {
1530 # Add an entry for each Repository configured for server
1531 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1532 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1533 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1534 $result= $fai_server_db->add_dbentry( {
1535 table => $table_name,
1536 primkey => ['server', 'release', 'tag'],
1537 server => $tmp_url,
1538 release => $tmp_release,
1539 sections => $tmp_sections,
1540 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1541 } );
1542 }
1543 }
1544 }
1545 }
1546 daemon_log("INFO: create_fai_server_db: finished", 5);
1548 # TODO: Find a way to post the 'create_packages_list_db' event
1549 &create_packages_list_db($ldap_handle);
1550 }
1552 $ldap_handle->disconnect;
1553 return $result;
1554 }
1556 sub run_create_fai_server_db {
1557 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1558 my $task = POE::Wheel::Run->new(
1559 Program => sub { &create_fai_server_db($table_name,$kernel) },
1560 StdoutEvent => "session_run_result",
1561 StderrEvent => "session_run_debug",
1562 CloseEvent => "session_run_done",
1563 );
1565 $heap->{task}->{ $task->ID } = $task;
1566 return;
1567 }
1570 sub create_fai_release_db {
1571 my ($table_name) = @_;
1572 my $result;
1574 my $ldap_handle = &get_ldap_handle();
1575 if(defined($ldap_handle)) {
1576 daemon_log("INFO: create_fai_release_db: start",5);
1577 my $mesg= $ldap_handle->search(
1578 base => $ldap_base,
1579 scope => 'sub',
1580 attrs => [],
1581 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1582 );
1583 if($mesg->{'resultCode'} == 0 &&
1584 $mesg->count != 0) {
1585 # Walk through all possible FAI container ou's
1586 my @sql_list;
1587 my $timestamp= &get_time();
1588 foreach my $ou (@{$mesg->{entries}}) {
1589 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1590 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1591 my @tmp_array=get_fai_release_entries($tmp_classes);
1592 if(@tmp_array) {
1593 foreach my $entry (@tmp_array) {
1594 if(defined($entry) && ref($entry) eq 'HASH') {
1595 my $sql=
1596 "INSERT INTO $table_name "
1597 ."(timestamp, release, class, type, state) VALUES ("
1598 .$timestamp.","
1599 ."'".$entry->{'release'}."',"
1600 ."'".$entry->{'class'}."',"
1601 ."'".$entry->{'type'}."',"
1602 ."'".$entry->{'state'}."')";
1603 push @sql_list, $sql;
1604 }
1605 }
1606 }
1607 }
1608 }
1609 daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1610 if(@sql_list) {
1611 unshift @sql_list, "DELETE FROM $table_name";
1612 $fai_server_db->exec_statementlist(\@sql_list);
1613 }
1614 daemon_log("DEBUG: Done with inserting",6);
1615 }
1616 daemon_log("INFO: create_fai_release_db: finished",5);
1617 }
1618 $ldap_handle->disconnect;
1619 return $result;
1620 }
1621 sub run_create_fai_release_db {
1622 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1623 my $task = POE::Wheel::Run->new(
1624 Program => sub { &create_fai_release_db($table_name) },
1625 StdoutEvent => "session_run_result",
1626 StderrEvent => "session_run_debug",
1627 CloseEvent => "session_run_done",
1628 );
1630 $heap->{task}->{ $task->ID } = $task;
1631 return;
1632 }
1634 sub get_fai_types {
1635 my $tmp_classes = shift || return undef;
1636 my @result;
1638 foreach my $type(keys %{$tmp_classes}) {
1639 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1640 my $entry = {
1641 type => $type,
1642 state => $tmp_classes->{$type}[0],
1643 };
1644 push @result, $entry;
1645 }
1646 }
1648 return @result;
1649 }
1651 sub get_fai_state {
1652 my $result = "";
1653 my $tmp_classes = shift || return $result;
1655 foreach my $type(keys %{$tmp_classes}) {
1656 if(defined($tmp_classes->{$type}[0])) {
1657 $result = $tmp_classes->{$type}[0];
1659 # State is equal for all types in class
1660 last;
1661 }
1662 }
1664 return $result;
1665 }
1667 sub resolve_fai_classes {
1668 my ($fai_base, $ldap_handle) = @_;
1669 my $result;
1670 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1671 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1672 my $fai_classes;
1674 daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1675 my $mesg= $ldap_handle->search(
1676 base => $fai_base,
1677 scope => 'sub',
1678 attrs => ['cn','objectClass','FAIstate'],
1679 filter => $fai_filter,
1680 );
1681 daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1683 if($mesg->{'resultCode'} == 0 &&
1684 $mesg->count != 0) {
1685 foreach my $entry (@{$mesg->{entries}}) {
1686 if($entry->exists('cn')) {
1687 my $tmp_dn= $entry->dn();
1689 # Skip classname and ou dn parts for class
1690 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1692 # Skip classes without releases
1693 if((!defined($tmp_release)) || length($tmp_release)==0) {
1694 next;
1695 }
1697 my $tmp_cn= $entry->get_value('cn');
1698 my $tmp_state= $entry->get_value('FAIstate');
1700 my $tmp_type;
1701 # Get FAI type
1702 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1703 if(grep $_ eq $oclass, @possible_fai_classes) {
1704 $tmp_type= $oclass;
1705 last;
1706 }
1707 }
1709 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1710 # A Subrelease
1711 my @sub_releases = split(/,/, $tmp_release);
1713 # Walk through subreleases and build hash tree
1714 my $hash;
1715 while(my $tmp_sub_release = pop @sub_releases) {
1716 $hash .= "\{'$tmp_sub_release'\}->";
1717 }
1718 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1719 } else {
1720 # A branch, no subrelease
1721 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1722 }
1723 } elsif (!$entry->exists('cn')) {
1724 my $tmp_dn= $entry->dn();
1725 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1727 # Skip classes without releases
1728 if((!defined($tmp_release)) || length($tmp_release)==0) {
1729 next;
1730 }
1732 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1733 # A Subrelease
1734 my @sub_releases= split(/,/, $tmp_release);
1736 # Walk through subreleases and build hash tree
1737 my $hash;
1738 while(my $tmp_sub_release = pop @sub_releases) {
1739 $hash .= "\{'$tmp_sub_release'\}->";
1740 }
1741 # Remove the last two characters
1742 chop($hash);
1743 chop($hash);
1745 eval('$fai_classes->'.$hash.'= {}');
1746 } else {
1747 # A branch, no subrelease
1748 if(!exists($fai_classes->{$tmp_release})) {
1749 $fai_classes->{$tmp_release} = {};
1750 }
1751 }
1752 }
1753 }
1755 # The hash is complete, now we can honor the copy-on-write based missing entries
1756 foreach my $release (keys %$fai_classes) {
1757 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1758 }
1759 }
1760 return $result;
1761 }
1763 sub apply_fai_inheritance {
1764 my $fai_classes = shift || return {};
1765 my $tmp_classes;
1767 # Get the classes from the branch
1768 foreach my $class (keys %{$fai_classes}) {
1769 # Skip subreleases
1770 if($class =~ /^ou=.*$/) {
1771 next;
1772 } else {
1773 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1774 }
1775 }
1777 # Apply to each subrelease
1778 foreach my $subrelease (keys %{$fai_classes}) {
1779 if($subrelease =~ /ou=/) {
1780 foreach my $tmp_class (keys %{$tmp_classes}) {
1781 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1782 $fai_classes->{$subrelease}->{$tmp_class} =
1783 deep_copy($tmp_classes->{$tmp_class});
1784 } else {
1785 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1786 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1787 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1788 deep_copy($tmp_classes->{$tmp_class}->{$type});
1789 }
1790 }
1791 }
1792 }
1793 }
1794 }
1796 # Find subreleases in deeper levels
1797 foreach my $subrelease (keys %{$fai_classes}) {
1798 if($subrelease =~ /ou=/) {
1799 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1800 if($subsubrelease =~ /ou=/) {
1801 apply_fai_inheritance($fai_classes->{$subrelease});
1802 }
1803 }
1804 }
1805 }
1807 return $fai_classes;
1808 }
1810 sub get_fai_release_entries {
1811 my $tmp_classes = shift || return;
1812 my $parent = shift || "";
1813 my @result = shift || ();
1815 foreach my $entry (keys %{$tmp_classes}) {
1816 if(defined($entry)) {
1817 if($entry =~ /^ou=.*$/) {
1818 my $release_name = $entry;
1819 $release_name =~ s/ou=//g;
1820 if(length($parent)>0) {
1821 $release_name = $parent."/".$release_name;
1822 }
1823 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1824 foreach my $bufentry(@bufentries) {
1825 push @result, $bufentry;
1826 }
1827 } else {
1828 my @types = get_fai_types($tmp_classes->{$entry});
1829 foreach my $type (@types) {
1830 push @result,
1831 {
1832 'class' => $entry,
1833 'type' => $type->{'type'},
1834 'release' => $parent,
1835 'state' => $type->{'state'},
1836 };
1837 }
1838 }
1839 }
1840 }
1842 return @result;
1843 }
1845 sub deep_copy {
1846 my $this = shift;
1847 if (not ref $this) {
1848 $this;
1849 } elsif (ref $this eq "ARRAY") {
1850 [map deep_copy($_), @$this];
1851 } elsif (ref $this eq "HASH") {
1852 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1853 } else { die "what type is $_?" }
1854 }
1857 sub session_run_result {
1858 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1859 $kernel->sig(CHLD => "child_reap");
1860 }
1862 sub session_run_debug {
1863 my $result = $_[ARG0];
1864 print STDERR "$result\n";
1865 }
1867 sub session_run_done {
1868 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1869 delete $heap->{task}->{$task_id};
1870 }
1872 sub create_sources_list {
1873 my ($ldap_handle) = @_;
1874 my $result="/tmp/gosa_si_tmp_sources_list";
1876 # Remove old file
1877 if(stat($result)) {
1878 unlink($result);
1879 }
1881 my $fh;
1882 open($fh, ">$result") or return undef;
1883 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1884 my $mesg=$ldap_handle->search(
1885 base => $ldap_server_dn,
1886 scope => 'base',
1887 attrs => 'FAIrepository',
1888 filter => 'objectClass=FAIrepositoryServer'
1889 );
1890 if($mesg->count) {
1891 foreach my $entry(@{$mesg->{'entries'}}) {
1892 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1893 my $line = "deb $server $release";
1894 $sections =~ s/,/ /g;
1895 $line.= " $sections";
1896 print $fh $line."\n";
1897 }
1898 }
1899 }
1900 close($fh);
1902 return $result;
1903 }
1905 sub create_packages_list_db {
1906 my ($ldap_handle, $sources_file) = @_ ;
1908 if (not defined $ldap_handle) {
1909 $ldap_handle= &get_ldap_handle();
1911 if (not defined $ldap_handle) {
1912 daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1913 return;
1914 }
1915 }
1917 if (not defined $sources_file) {
1918 $sources_file = &create_sources_list($ldap_handle);
1919 }
1921 my $line;
1922 daemon_log("INFO: create_packages_list_db: start", 5);
1924 open(CONFIG, "<$sources_file") or do {
1925 daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1926 return;
1927 };
1929 # Read lines
1930 while ($line = <CONFIG>){
1931 # Unify
1932 chop($line);
1933 $line =~ s/^\s+//;
1934 $line =~ s/^\s+/ /;
1936 # Strip comments
1937 $line =~ s/#.*$//g;
1939 # Skip empty lines
1940 if ($line =~ /^\s*$/){
1941 next;
1942 }
1944 # Interpret deb line
1945 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1946 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1947 my $section;
1948 foreach $section (split(' ', $sections)){
1949 &parse_package_info( $baseurl, $dist, $section );
1950 }
1951 }
1952 }
1954 close (CONFIG);
1956 daemon_log("INFO: create_packages_list_db: finished", 5);
1957 return;
1958 }
1960 sub run_create_packages_list_db {
1961 my ($session, $heap) = @_[SESSION, HEAP];
1962 my $task = POE::Wheel::Run->new(
1963 Program => sub {&create_packages_list_db},
1964 StdoutEvent => "session_run_result",
1965 StderrEvent => "session_run_debug",
1966 CloseEvent => "session_run_done",
1967 );
1968 $heap->{task}->{ $task->ID } = $task;
1969 }
1971 sub parse_package_info {
1972 my ($baseurl, $dist, $section)= @_;
1973 my ($package);
1975 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1976 $repo_dirs{ "${repo_path}/pool" } = 1;
1978 foreach $package ("Packages.gz"){
1979 daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1980 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1981 parse_package( "$outdir/$dist/$section", $dist, $path );
1982 }
1983 find(\&cleanup_and_extract, keys( %repo_dirs ) );
1984 }
1986 sub get_package {
1987 my ($url, $dest)= @_;
1989 my $tpath = dirname($dest);
1990 -d "$tpath" || mkpath "$tpath";
1992 # This is ugly, but I've no time to take a look at "how it works in perl"
1993 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1994 system("gunzip -cd '$dest' > '$dest.in'");
1995 unlink($dest);
1996 } else {
1997 daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1998 }
1999 return 0;
2000 }
2002 sub parse_package {
2003 my ($path, $dist, $srv_path)= @_;
2004 my ($package, $version, $section, $description);
2005 my @sql_list;
2006 my $PACKAGES;
2008 if(not stat("$path.in")) {
2009 daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2010 return;
2011 }
2013 open($PACKAGES, "<$path.in");
2014 if(not defined($PACKAGES)) {
2015 daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1);
2016 return;
2017 }
2019 # Read lines
2020 while (<$PACKAGES>){
2021 my $line = $_;
2022 # Unify
2023 chop($line);
2025 # Use empty lines as a trigger
2026 if ($line =~ /^\s*$/){
2027 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2028 push(@sql_list, $sql);
2029 $package = "none";
2030 $version = "none";
2031 $section = "none";
2032 $description = "none";
2033 next;
2034 }
2036 # Trigger for package name
2037 if ($line =~ /^Package:\s/){
2038 ($package)= ($line =~ /^Package: (.*)$/);
2039 next;
2040 }
2042 # Trigger for version
2043 if ($line =~ /^Version:\s/){
2044 ($version)= ($line =~ /^Version: (.*)$/);
2045 next;
2046 }
2048 # Trigger for description
2049 if ($line =~ /^Description:\s/){
2050 ($description)= ($line =~ /^Description: (.*)$/);
2051 next;
2052 }
2054 # Trigger for section
2055 if ($line =~ /^Section:\s/){
2056 ($section)= ($line =~ /^Section: (.*)$/);
2057 next;
2058 }
2060 # Trigger for filename
2061 if ($line =~ /^Filename:\s/){
2062 my ($filename) = ($line =~ /^Filename: (.*)$/);
2063 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2064 next;
2065 }
2066 }
2068 close( $PACKAGES );
2069 unlink( "$path.in" );
2071 $packages_list_db->exec_statementlist(\@sql_list);
2072 }
2074 sub store_fileinfo {
2075 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2077 my %fileinfo = (
2078 'package' => $package,
2079 'dist' => $dist,
2080 'version' => $vers,
2081 );
2083 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2084 }
2086 sub cleanup_and_extract {
2087 my $fileinfo = $repo_files{ $File::Find::name };
2089 if( defined $fileinfo ) {
2091 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2092 my $sql;
2093 my $package = $fileinfo->{ 'package' };
2094 my $newver = $fileinfo->{ 'version' };
2096 mkpath($dir);
2097 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2099 if( -f "$dir/DEBIAN/templates" ) {
2101 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2103 my $tmpl= "";
2104 {
2105 local $/=undef;
2106 open FILE, "$dir/DEBIAN/templates";
2107 $tmpl = &encode_base64(<FILE>);
2108 close FILE;
2109 }
2110 rmtree("$dir/DEBIAN/templates");
2112 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2114 } else {
2115 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2116 }
2118 my $res= $main::packages_list_db->update_dbentry($sql);
2119 }
2120 }
2123 #==== MAIN = main ==============================================================
2124 # parse commandline options
2125 Getopt::Long::Configure( "bundling" );
2126 GetOptions("h|help" => \&usage,
2127 "c|config=s" => \$cfg_file,
2128 "f|foreground" => \$foreground,
2129 "v|verbose+" => \$verbose,
2130 "no-bus+" => \$no_bus,
2131 "no-arp+" => \$no_arp,
2132 );
2134 # read and set config parameters
2135 &check_cmdline_param ;
2136 &read_configfile;
2137 &check_pid;
2139 $SIG{CHLD} = 'IGNORE';
2141 # forward error messages to logfile
2142 if( ! $foreground ) {
2143 open( STDIN, '+>/dev/null' );
2144 open( STDOUT, '+>&STDIN' );
2145 open( STDERR, '+>&STDIN' );
2146 }
2148 # Just fork, if we are not in foreground mode
2149 if( ! $foreground ) {
2150 chdir '/' or die "Can't chdir to /: $!";
2151 $pid = fork;
2152 setsid or die "Can't start a new session: $!";
2153 umask 0;
2154 } else {
2155 $pid = $$;
2156 }
2158 # Do something useful - put our PID into the pid_file
2159 if( 0 != $pid ) {
2160 open( LOCK_FILE, ">$pid_file" );
2161 print LOCK_FILE "$pid\n";
2162 close( LOCK_FILE );
2163 if( !$foreground ) {
2164 exit( 0 )
2165 };
2166 }
2168 daemon_log(" ", 1);
2169 daemon_log("$0 started!", 1);
2171 if ($no_bus > 0) {
2172 $bus_activ = "false"
2173 }
2175 # connect to gosa-si job queue
2176 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2177 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2179 # connect to known_clients_db
2180 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2181 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2183 # connect to known_server_db
2184 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2185 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2187 # connect to login_usr_db
2188 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2189 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2191 # connect to fai_server_db and fai_release_db
2192 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2193 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2194 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2196 # connect to packages_list_db
2197 unlink($packages_list_file_name);
2198 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2199 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2201 # connect to messaging_db
2202 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2203 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2206 # create xml object used for en/decrypting
2207 $xml = new XML::Simple();
2209 # create socket for incoming xml messages
2211 POE::Component::Server::TCP->new(
2212 Port => $server_port,
2213 ClientInput => sub {
2214 my ($kernel, $input) = @_[KERNEL, ARG0];
2215 push(@tasks, $input);
2216 $kernel->yield("next_task");
2217 },
2218 InlineStates => {
2219 next_task => \&next_task,
2220 task_result => \&handle_task_result,
2221 task_done => \&handle_task_done,
2222 task_debug => \&handle_task_debug,
2223 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2224 }
2225 );
2227 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2229 # create session for repeatedly checking the job queue for jobs
2230 POE::Session->create(
2231 inline_states => {
2232 _start => \&_start,
2233 sig_handler => \&sig_handler,
2234 watch_for_new_jobs => \&watch_for_new_jobs,
2235 watch_for_done_jobs => \&watch_for_done_jobs,
2236 create_packages_list_db => \&run_create_packages_list_db,
2237 create_fai_server_db => \&run_create_fai_server_db,
2238 create_fai_release_db => \&run_create_fai_release_db,
2239 session_run_result => \&session_run_result,
2240 session_run_debug => \&session_run_debug,
2241 session_run_done => \&session_run_done,
2242 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2243 }
2244 );
2247 # import all modules
2248 &import_modules;
2250 # check wether all modules are gosa-si valid passwd check
2252 POE::Kernel->run();
2253 exit;