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