657f40e37e5b477aa50c6f57906038eef61049d1
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}) {
1292 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1293 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1294 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1296 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1297 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1298 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1300 # expect macaddress is unique!!!!!!
1301 my $target = $res_hash->{1}->{hostname};
1303 # change header
1304 $job_msg =~ s/<header>job_/<header>gosa_/;
1306 # add sqlite_id
1307 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1309 $job_msg =~ /<header>(\S+)<\/header>/;
1310 my $header = $1 ;
1311 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1313 # update status in job queue to 'processing'
1314 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1315 my $res = $job_db->update_dbentry($sql_statement);
1316 }
1317 }
1319 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1320 }
1323 sub get_ldap_handle {
1324 my ($session_id) = @_;
1325 my $heap;
1326 my $ldap_handle;
1328 if (not defined $session_id ) { $session_id = 0 };
1330 if ($session_id == 0) {
1331 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1332 $ldap_handle = Net::LDAP->new( $ldap_uri );
1333 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1335 } else {
1336 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1337 if( defined $session_reference ) {
1338 $heap = $session_reference->get_heap();
1339 }
1341 if (not defined $heap) {
1342 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1343 return;
1344 }
1346 # TODO: This "if" is nonsense, because it doesn't prove that the
1347 # used handle is still valid - or if we've to reconnect...
1348 #if (not exists $heap->{ldap_handle}) {
1349 $ldap_handle = Net::LDAP->new( $ldap_uri );
1350 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1351 $heap->{ldap_handle} = $ldap_handle;
1352 #}
1353 }
1354 return $ldap_handle;
1355 }
1358 sub change_fai_state {
1359 my ($st, $targets, $session_id) = @_;
1360 $session_id = 0 if not defined $session_id;
1361 # Set FAI state to localboot
1362 my %mapActions= (
1363 reboot => '',
1364 update => 'softupdate',
1365 localboot => 'localboot',
1366 reinstall => 'install',
1367 rescan => '',
1368 wake => '',
1369 memcheck => 'memcheck',
1370 sysinfo => 'sysinfo',
1371 install => 'install',
1372 );
1374 # Return if this is unknown
1375 if (!exists $mapActions{ $st }){
1376 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1377 return;
1378 }
1380 my $state= $mapActions{ $st };
1382 my $ldap_handle = &get_ldap_handle($session_id);
1383 if( defined($ldap_handle) ) {
1385 # Build search filter for hosts
1386 my $search= "(&(objectClass=GOhard)";
1387 foreach (@{$targets}){
1388 $search.= "(macAddress=$_)";
1389 }
1390 $search.= ")";
1392 # If there's any host inside of the search string, procress them
1393 if (!($search =~ /macAddress/)){
1394 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1395 return;
1396 }
1398 # Perform search for Unit Tag
1399 my $mesg = $ldap_handle->search(
1400 base => $ldap_base,
1401 scope => 'sub',
1402 attrs => ['dn', 'FAIstate', 'objectClass'],
1403 filter => "$search"
1404 );
1406 if ($mesg->count) {
1407 my @entries = $mesg->entries;
1408 foreach my $entry (@entries) {
1409 # Only modify entry if it is not set to '$state'
1410 if ($entry->get_value("FAIstate") ne "$state"){
1411 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1412 my $result;
1413 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1414 if (exists $tmp{'FAIobject'}){
1415 if ($state eq ''){
1416 $result= $ldap_handle->modify($entry->dn, changes => [
1417 delete => [ FAIstate => [] ] ]);
1418 } else {
1419 $result= $ldap_handle->modify($entry->dn, changes => [
1420 replace => [ FAIstate => $state ] ]);
1421 }
1422 } elsif ($state ne ''){
1423 $result= $ldap_handle->modify($entry->dn, changes => [
1424 add => [ objectClass => 'FAIobject' ],
1425 add => [ FAIstate => $state ] ]);
1426 }
1428 # Errors?
1429 if ($result->code){
1430 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1431 }
1432 } else {
1433 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1434 }
1435 }
1436 }
1437 # if no ldap handle defined
1438 } else {
1439 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1440 }
1442 }
1445 sub change_goto_state {
1446 my ($st, $targets, $session_id) = @_;
1447 $session_id = 0 if not defined $session_id;
1449 # Switch on or off?
1450 my $state= $st eq 'active' ? 'active': 'locked';
1452 my $ldap_handle = &get_ldap_handle($session_id);
1453 if( defined($ldap_handle) ) {
1455 # Build search filter for hosts
1456 my $search= "(&(objectClass=GOhard)";
1457 foreach (@{$targets}){
1458 $search.= "(macAddress=$_)";
1459 }
1460 $search.= ")";
1462 # If there's any host inside of the search string, procress them
1463 if (!($search =~ /macAddress/)){
1464 return;
1465 }
1467 # Perform search for Unit Tag
1468 my $mesg = $ldap_handle->search(
1469 base => $ldap_base,
1470 scope => 'sub',
1471 attrs => ['dn', 'gotoMode'],
1472 filter => "$search"
1473 );
1475 if ($mesg->count) {
1476 my @entries = $mesg->entries;
1477 foreach my $entry (@entries) {
1479 # Only modify entry if it is not set to '$state'
1480 if ($entry->get_value("gotoMode") ne $state){
1482 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1483 my $result;
1484 $result= $ldap_handle->modify($entry->dn, changes => [
1485 replace => [ gotoMode => $state ] ]);
1487 # Errors?
1488 if ($result->code){
1489 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1490 }
1492 }
1493 }
1494 }
1496 }
1497 }
1500 sub create_fai_server_db {
1501 my ($table_name, $kernel) = @_;
1502 my $result;
1503 my $ldap_handle = &get_ldap_handle();
1504 if(defined($ldap_handle)) {
1505 daemon_log("INFO: create_fai_server_db: start", 5);
1506 my $mesg= $ldap_handle->search(
1507 base => $ldap_base,
1508 scope => 'sub',
1509 attrs => ['FAIrepository', 'gosaUnitTag'],
1510 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1511 );
1512 if($mesg->{'resultCode'} == 0 &&
1513 $mesg->count != 0) {
1514 foreach my $entry (@{$mesg->{entries}}) {
1515 if($entry->exists('FAIrepository')) {
1516 # Add an entry for each Repository configured for server
1517 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1518 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1519 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1520 $result= $fai_server_db->add_dbentry( {
1521 table => $table_name,
1522 primkey => ['server', 'release', 'tag'],
1523 server => $tmp_url,
1524 release => $tmp_release,
1525 sections => $tmp_sections,
1526 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1527 } );
1528 }
1529 }
1530 }
1531 }
1532 daemon_log("INFO: create_fai_server_db: finished", 5);
1534 # TODO: Find a way to post the 'create_packages_list_db' event
1535 &create_packages_list_db($ldap_handle);
1536 }
1538 $ldap_handle->disconnect;
1539 return $result;
1540 }
1542 sub run_create_fai_server_db {
1543 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1544 my $task = POE::Wheel::Run->new(
1545 Program => sub { &create_fai_server_db($table_name,$kernel) },
1546 StdoutEvent => "session_run_result",
1547 StderrEvent => "session_run_debug",
1548 CloseEvent => "session_run_done",
1549 );
1551 $heap->{task}->{ $task->ID } = $task;
1552 return;
1553 }
1556 sub create_fai_release_db {
1557 my ($table_name) = @_;
1558 my $result;
1560 my $ldap_handle = &get_ldap_handle();
1561 if(defined($ldap_handle)) {
1562 daemon_log("INFO: create_fai_release_db: start",5);
1563 my $mesg= $ldap_handle->search(
1564 base => $ldap_base,
1565 scope => 'sub',
1566 attrs => [],
1567 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1568 );
1569 if($mesg->{'resultCode'} == 0 &&
1570 $mesg->count != 0) {
1571 # Walk through all possible FAI container ou's
1572 my @sql_list;
1573 my $timestamp= &get_time();
1574 foreach my $ou (@{$mesg->{entries}}) {
1575 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle);
1576 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1577 my @tmp_array=get_fai_release_entries($tmp_classes);
1578 if(@tmp_array) {
1579 foreach my $entry (@tmp_array) {
1580 if(defined($entry) && ref($entry) eq 'HASH') {
1581 my $sql=
1582 "INSERT INTO $table_name "
1583 ."(timestamp, release, class, type, state) VALUES ("
1584 .$timestamp.","
1585 ."'".$entry->{'release'}."',"
1586 ."'".$entry->{'class'}."',"
1587 ."'".$entry->{'type'}."',"
1588 ."'".$entry->{'state'}."')";
1589 push @sql_list, $sql;
1590 }
1591 }
1592 }
1593 }
1594 }
1595 daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1596 if(@sql_list) {
1597 unshift @sql_list, "DELETE FROM $table_name";
1598 $fai_server_db->exec_statementlist(\@sql_list);
1599 }
1600 daemon_log("DEBUG: Done with inserting",6);
1601 }
1602 daemon_log("INFO: create_fai_release_db: finished",5);
1603 }
1604 $ldap_handle->disconnect;
1605 return $result;
1606 }
1607 sub run_create_fai_release_db {
1608 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1609 my $task = POE::Wheel::Run->new(
1610 Program => sub { &create_fai_release_db($table_name) },
1611 StdoutEvent => "session_run_result",
1612 StderrEvent => "session_run_debug",
1613 CloseEvent => "session_run_done",
1614 );
1616 $heap->{task}->{ $task->ID } = $task;
1617 return;
1618 }
1620 sub get_fai_types {
1621 my $tmp_classes = shift || return undef;
1622 my @result;
1624 foreach my $type(keys %{$tmp_classes}) {
1625 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1626 my $entry = {
1627 type => $type,
1628 state => $tmp_classes->{$type}[0],
1629 };
1630 push @result, $entry;
1631 }
1632 }
1634 return @result;
1635 }
1637 sub get_fai_state {
1638 my $result = "";
1639 my $tmp_classes = shift || return $result;
1641 foreach my $type(keys %{$tmp_classes}) {
1642 if(defined($tmp_classes->{$type}[0])) {
1643 $result = $tmp_classes->{$type}[0];
1645 # State is equal for all types in class
1646 last;
1647 }
1648 }
1650 return $result;
1651 }
1653 sub resolve_fai_classes {
1654 my ($fai_base, $ldap_handle) = @_;
1655 my $result;
1656 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1657 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1658 my $fai_classes;
1660 daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1661 my $mesg= $ldap_handle->search(
1662 base => $fai_base,
1663 scope => 'sub',
1664 attrs => ['cn','objectClass','FAIstate'],
1665 filter => $fai_filter,
1666 );
1667 daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1669 if($mesg->{'resultCode'} == 0 &&
1670 $mesg->count != 0) {
1671 foreach my $entry (@{$mesg->{entries}}) {
1672 if($entry->exists('cn')) {
1673 my $tmp_dn= $entry->dn();
1675 # Skip classname and ou dn parts for class
1676 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1678 # Skip classes without releases
1679 if((!defined($tmp_release)) || length($tmp_release)==0) {
1680 next;
1681 }
1683 my $tmp_cn= $entry->get_value('cn');
1684 my $tmp_state= $entry->get_value('FAIstate');
1686 my $tmp_type;
1687 # Get FAI type
1688 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1689 if(grep $_ eq $oclass, @possible_fai_classes) {
1690 $tmp_type= $oclass;
1691 last;
1692 }
1693 }
1695 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1696 # A Subrelease
1697 my @sub_releases = split(/,/, $tmp_release);
1699 # Walk through subreleases and build hash tree
1700 my $hash;
1701 while(my $tmp_sub_release = pop @sub_releases) {
1702 $hash .= "\{'$tmp_sub_release'\}->";
1703 }
1704 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1705 } else {
1706 # A branch, no subrelease
1707 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1708 }
1709 } elsif (!$entry->exists('cn')) {
1710 my $tmp_dn= $entry->dn();
1711 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1713 # Skip classes without releases
1714 if((!defined($tmp_release)) || length($tmp_release)==0) {
1715 next;
1716 }
1718 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1719 # A Subrelease
1720 my @sub_releases= split(/,/, $tmp_release);
1722 # Walk through subreleases and build hash tree
1723 my $hash;
1724 while(my $tmp_sub_release = pop @sub_releases) {
1725 $hash .= "\{'$tmp_sub_release'\}->";
1726 }
1727 # Remove the last two characters
1728 chop($hash);
1729 chop($hash);
1731 eval('$fai_classes->'.$hash.'= {}');
1732 } else {
1733 # A branch, no subrelease
1734 if(!exists($fai_classes->{$tmp_release})) {
1735 $fai_classes->{$tmp_release} = {};
1736 }
1737 }
1738 }
1739 }
1741 # The hash is complete, now we can honor the copy-on-write based missing entries
1742 foreach my $release (keys %$fai_classes) {
1743 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1744 }
1745 }
1746 return $result;
1747 }
1749 sub apply_fai_inheritance {
1750 my $fai_classes = shift || return {};
1751 my $tmp_classes;
1753 # Get the classes from the branch
1754 foreach my $class (keys %{$fai_classes}) {
1755 # Skip subreleases
1756 if($class =~ /^ou=.*$/) {
1757 next;
1758 } else {
1759 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1760 }
1761 }
1763 # Apply to each subrelease
1764 foreach my $subrelease (keys %{$fai_classes}) {
1765 if($subrelease =~ /ou=/) {
1766 foreach my $tmp_class (keys %{$tmp_classes}) {
1767 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1768 $fai_classes->{$subrelease}->{$tmp_class} =
1769 deep_copy($tmp_classes->{$tmp_class});
1770 } else {
1771 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1772 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1773 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1774 deep_copy($tmp_classes->{$tmp_class}->{$type});
1775 }
1776 }
1777 }
1778 }
1779 }
1780 }
1782 # Find subreleases in deeper levels
1783 foreach my $subrelease (keys %{$fai_classes}) {
1784 if($subrelease =~ /ou=/) {
1785 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1786 if($subsubrelease =~ /ou=/) {
1787 apply_fai_inheritance($fai_classes->{$subrelease});
1788 }
1789 }
1790 }
1791 }
1793 return $fai_classes;
1794 }
1796 sub get_fai_release_entries {
1797 my $tmp_classes = shift || return;
1798 my $parent = shift || "";
1799 my @result = shift || ();
1801 foreach my $entry (keys %{$tmp_classes}) {
1802 if(defined($entry)) {
1803 if($entry =~ /^ou=.*$/) {
1804 my $release_name = $entry;
1805 $release_name =~ s/ou=//g;
1806 if(length($parent)>0) {
1807 $release_name = $parent."/".$release_name;
1808 }
1809 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1810 foreach my $bufentry(@bufentries) {
1811 push @result, $bufentry;
1812 }
1813 } else {
1814 my @types = get_fai_types($tmp_classes->{$entry});
1815 foreach my $type (@types) {
1816 push @result,
1817 {
1818 'class' => $entry,
1819 'type' => $type->{'type'},
1820 'release' => $parent,
1821 'state' => $type->{'state'},
1822 };
1823 }
1824 }
1825 }
1826 }
1828 return @result;
1829 }
1831 sub deep_copy {
1832 my $this = shift;
1833 if (not ref $this) {
1834 $this;
1835 } elsif (ref $this eq "ARRAY") {
1836 [map deep_copy($_), @$this];
1837 } elsif (ref $this eq "HASH") {
1838 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1839 } else { die "what type is $_?" }
1840 }
1843 sub session_run_result {
1844 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
1845 $kernel->sig(CHLD => "child_reap");
1846 }
1848 sub session_run_debug {
1849 my $result = $_[ARG0];
1850 print STDERR "$result\n";
1851 }
1853 sub session_run_done {
1854 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1855 delete $heap->{task}->{$task_id};
1856 }
1858 sub create_sources_list {
1859 my ($ldap_handle) = @_;
1860 my $result="/tmp/gosa_si_tmp_sources_list";
1862 # Remove old file
1863 if(stat($result)) {
1864 unlink($result);
1865 }
1867 my $fh;
1868 open($fh, ">$result") or return undef;
1869 if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1870 my $mesg=$ldap_handle->search(
1871 base => $ldap_server_dn,
1872 scope => 'base',
1873 attrs => 'FAIrepository',
1874 filter => 'objectClass=FAIrepositoryServer'
1875 );
1876 if($mesg->count) {
1877 foreach my $entry(@{$mesg->{'entries'}}) {
1878 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1879 my $line = "deb $server $release";
1880 $sections =~ s/,/ /g;
1881 $line.= " $sections";
1882 print $fh $line."\n";
1883 }
1884 }
1885 }
1886 close($fh);
1888 return $result;
1889 }
1891 sub create_packages_list_db {
1892 my ($ldap_handle, $sources_file) = @_ ;
1894 if (not defined $ldap_handle) {
1895 daemon_log("0 ERROR: no ldap_handle available to create_packages_list_db", 1);
1896 return;
1897 }
1898 if (not defined $sources_file) {
1899 $sources_file = &create_sources_list($ldap_handle);
1900 }
1902 my $line;
1903 daemon_log("INFO: create_packages_list_db: start", 5);
1905 open(CONFIG, "<$sources_file") or do {
1906 daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1907 return;
1908 };
1910 # Read lines
1911 while ($line = <CONFIG>){
1912 # Unify
1913 chop($line);
1914 $line =~ s/^\s+//;
1915 $line =~ s/^\s+/ /;
1917 # Strip comments
1918 $line =~ s/#.*$//g;
1920 # Skip empty lines
1921 if ($line =~ /^\s*$/){
1922 next;
1923 }
1925 # Interpret deb line
1926 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1927 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1928 my $section;
1929 foreach $section (split(' ', $sections)){
1930 &parse_package_info( $baseurl, $dist, $section );
1931 }
1932 }
1933 }
1935 close (CONFIG);
1937 daemon_log("INFO: create_packages_list_db: finished", 5);
1938 return;
1939 }
1941 sub run_create_packages_list_db {
1942 my ($session, $heap) = @_[SESSION, HEAP];
1943 my $task = POE::Wheel::Run->new(
1944 Program => sub {&create_packages_list_db},
1945 StdoutEvent => "session_run_result",
1946 StderrEvent => "session_run_debug",
1947 CloseEvent => "session_run_done",
1948 );
1949 $heap->{task}->{ $task->ID } = $task;
1950 }
1952 sub parse_package_info {
1953 my ($baseurl, $dist, $section)= @_;
1954 my ($package);
1956 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1957 $repo_dirs{ "${repo_path}/pool" } = 1;
1959 foreach $package ("Packages.gz"){
1960 daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1961 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1962 parse_package( "$outdir/$dist/$section", $dist, $path );
1963 }
1964 find(\&cleanup_and_extract, keys( %repo_dirs ) );
1965 }
1967 sub get_package {
1968 my ($url, $dest)= @_;
1970 my $tpath = dirname($dest);
1971 -d "$tpath" || mkpath "$tpath";
1973 # This is ugly, but I've no time to take a look at "how it works in perl"
1974 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1975 system("gunzip -cd '$dest' > '$dest.in'");
1976 unlink($dest);
1977 } else {
1978 daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1979 }
1980 return 0;
1981 }
1983 sub parse_package {
1984 my ($path, $dist, $srv_path)= @_;
1985 my ($package, $version, $section, $description);
1986 my @sql_list;
1987 my $PACKAGES;
1989 if(not stat("$path.in")) {
1990 daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1991 return;
1992 }
1994 open($PACKAGES, "<$path.in");
1995 if(not defined($PACKAGES)) {
1996 daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1);
1997 return;
1998 }
2000 # Read lines
2001 while (<$PACKAGES>){
2002 my $line = $_;
2003 # Unify
2004 chop($line);
2006 # Use empty lines as a trigger
2007 if ($line =~ /^\s*$/){
2008 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
2009 push(@sql_list, $sql);
2010 $package = "none";
2011 $version = "none";
2012 $section = "none";
2013 $description = "none";
2014 next;
2015 }
2017 # Trigger for package name
2018 if ($line =~ /^Package:\s/){
2019 ($package)= ($line =~ /^Package: (.*)$/);
2020 next;
2021 }
2023 # Trigger for version
2024 if ($line =~ /^Version:\s/){
2025 ($version)= ($line =~ /^Version: (.*)$/);
2026 next;
2027 }
2029 # Trigger for description
2030 if ($line =~ /^Description:\s/){
2031 ($description)= ($line =~ /^Description: (.*)$/);
2032 next;
2033 }
2035 # Trigger for section
2036 if ($line =~ /^Section:\s/){
2037 ($section)= ($line =~ /^Section: (.*)$/);
2038 next;
2039 }
2041 # Trigger for filename
2042 if ($line =~ /^Filename:\s/){
2043 my ($filename) = ($line =~ /^Filename: (.*)$/);
2044 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2045 next;
2046 }
2047 }
2049 close( $PACKAGES );
2050 unlink( "$path.in" );
2052 $packages_list_db->exec_statementlist(\@sql_list);
2053 }
2055 sub store_fileinfo {
2056 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2058 my %fileinfo = (
2059 'package' => $package,
2060 'dist' => $dist,
2061 'version' => $vers,
2062 );
2064 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2065 }
2067 sub cleanup_and_extract {
2068 my $fileinfo = $repo_files{ $File::Find::name };
2070 if( defined $fileinfo ) {
2072 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2073 my $sql;
2074 my $package = $fileinfo->{ 'package' };
2075 my $newver = $fileinfo->{ 'version' };
2077 mkpath($dir);
2078 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2080 if( -f "$dir/DEBIAN/templates" ) {
2082 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2084 my $tmpl= "";
2085 {
2086 local $/=undef;
2087 open FILE, "$dir/DEBIAN/templates";
2088 $tmpl = &encode_base64(<FILE>);
2089 close FILE;
2090 }
2091 rmtree("$dir/DEBIAN/templates");
2093 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2095 } else {
2096 $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2097 }
2099 my $res= $main::packages_list_db->update_dbentry($sql);
2100 }
2101 }
2104 #==== MAIN = main ==============================================================
2105 # parse commandline options
2106 Getopt::Long::Configure( "bundling" );
2107 GetOptions("h|help" => \&usage,
2108 "c|config=s" => \$cfg_file,
2109 "f|foreground" => \$foreground,
2110 "v|verbose+" => \$verbose,
2111 "no-bus+" => \$no_bus,
2112 "no-arp+" => \$no_arp,
2113 );
2115 # read and set config parameters
2116 &check_cmdline_param ;
2117 &read_configfile;
2118 &check_pid;
2120 $SIG{CHLD} = 'IGNORE';
2122 # forward error messages to logfile
2123 if( ! $foreground ) {
2124 open( STDIN, '+>/dev/null' );
2125 open( STDOUT, '+>&STDIN' );
2126 open( STDERR, '+>&STDIN' );
2127 }
2129 # Just fork, if we are not in foreground mode
2130 if( ! $foreground ) {
2131 chdir '/' or die "Can't chdir to /: $!";
2132 $pid = fork;
2133 setsid or die "Can't start a new session: $!";
2134 umask 0;
2135 } else {
2136 $pid = $$;
2137 }
2139 # Do something useful - put our PID into the pid_file
2140 if( 0 != $pid ) {
2141 open( LOCK_FILE, ">$pid_file" );
2142 print LOCK_FILE "$pid\n";
2143 close( LOCK_FILE );
2144 if( !$foreground ) {
2145 exit( 0 )
2146 };
2147 }
2149 daemon_log(" ", 1);
2150 daemon_log("$0 started!", 1);
2152 if ($no_bus > 0) {
2153 $bus_activ = "false"
2154 }
2156 # connect to gosa-si job queue
2157 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2158 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2160 # connect to known_clients_db
2161 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2162 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2164 # connect to known_server_db
2165 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2166 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2168 # connect to login_usr_db
2169 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2170 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2172 # connect to fai_server_db and fai_release_db
2173 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2174 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2175 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2177 # connect to packages_list_db
2178 unlink($packages_list_file_name);
2179 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2180 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2182 # connect to messaging_db
2183 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2184 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2187 # create xml object used for en/decrypting
2188 $xml = new XML::Simple();
2190 # create socket for incoming xml messages
2192 POE::Component::Server::TCP->new(
2193 Port => $server_port,
2194 ClientInput => sub {
2195 my ($kernel, $input) = @_[KERNEL, ARG0];
2196 push(@tasks, $input);
2197 $kernel->yield("next_task");
2198 },
2199 InlineStates => {
2200 next_task => \&next_task,
2201 task_result => \&handle_task_result,
2202 task_done => \&handle_task_done,
2203 task_debug => \&handle_task_debug,
2204 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2205 }
2206 );
2208 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2210 # create session for repeatedly checking the job queue for jobs
2211 POE::Session->create(
2212 inline_states => {
2213 _start => \&_start,
2214 sig_handler => \&sig_handler,
2215 watch_for_new_jobs => \&watch_for_new_jobs,
2216 watch_for_done_jobs => \&watch_for_done_jobs,
2217 create_packages_list_db => \&run_create_packages_list_db,
2218 create_fai_server_db => \&run_create_fai_server_db,
2219 create_fai_release_db => \&run_create_fai_release_db,
2220 session_run_result => \&session_run_result,
2221 session_run_debug => \&session_run_debug,
2222 session_run_done => \&session_run_done,
2223 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2224 }
2225 );
2228 # import all modules
2229 &import_modules;
2231 # check wether all modules are gosa-si valid passwd check
2233 POE::Kernel->run();
2234 exit;