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