88e13681e45cdca30dd594b9091c1d8596065036
1 #!/usr/bin/perl
2 #*********************************************************************
3 #
4 # gosa-si-client -- client for the gosa-si-server
5 #
6 # (c) 2007-2009 by Andreas Rettenberger <rettenberger@gonicus.de>
7 # (c) 2008-2010 by Cajus Pollmeier <pollmeier@gonicus.de>
8 # (c) 2008-2009 by Jan Wenzel <wenzel@gonicus.de>
9 # (c) 2010 by Benoit Mortier <benoit.mortier@opensides.be>
10 #
11 #*********************************************************************
14 =head1 NAME
16 gosa-si-server -Support infrastructure for GOsa
18 =head1 SYNOPSIS
20 gosa-si-server [-hvf] [-c config] [-x dump ]
22 =head1 OPTIONS
24 B<-h>, B<--help>
25 print out this help message
27 B<-v>, B<--verbose>
28 be verbose (multiple v's will increase verbosity)
29 -v ERROR level
30 -vvv WARNING + ERROR level
31 -vvvvv INFO + WARNING level
32 -vvvvvvv DEBUG + INFO level
33 -vvvvvvvvv in and out going xml messages will be displayed
35 B<-f>, B<--foreground>
36 foregroud, process will not be forked to background
38 B<-c> I<file>, B<--config=>I<file>
39 configuration file, default F</etc/gosa-si/server.conf>
41 B<--no-arp>
42 starts script without connection to arp module
44 B<-d> <int>
45 if verbose level is higher than 7 'v' specified parts can be debugged
47 1 : receiving messages
48 2 : sending messages
49 4 : encrypting/decrypting messages
50 8 : verification if a message complies gosa-si requirements
51 16 : message processing
52 32 : ldap connectivity
53 64 : database status and connectivity
54 128 : main process
55 256 : creation of packages_list_db
56 512 : ARP debug information
58 B<-x> <dump>
59 dump configuration to stdout
60 ( 1 = current, 2 = default )
62 =head1 DESCRIPTION
64 gosa-si-server belongs to the support infrastructure of GOsa. Several gosa-si-clients can connect to one gosa-si-server. The server take care of the message forwarding from GOsa to si-clients. At the client site each message is related to a working instruction which will be executed there. Depending of the message an answer from the client to GOsa via the server is possible. Additional to answers clients reporting events or information to the server. The server registers himself at other servers in network and shares his knowledge with them. So messages to clients which are no registrated locally will be forward to the client corresponding server. The communication within the complete SI nework is realised by XML messages.
67 =head1 BUGS
69 Please report any bugs, or post any suggestions, to the GOsa mailing list <gosa-devel@oss.gonicus.de> or to <https://oss.gonicus.de/labs/gosa>
72 =head1 LICENCE AND COPYRIGHT
74 This code is part of GOsa (L<http://www.gosa-project.org>)
76 Copyright (C) 2003-2010 GONICUS GmbH
78 This program is distributed in the hope that it will be useful,
79 but WITHOUT ANY WARRANTY; without even the implied warranty of
80 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
81 GNU General Public License for more details.
83 =cut
85 use strict;
86 use warnings;
88 use Getopt::Long;
89 use Config::IniFiles;
90 use IO::Socket::INET;
91 use IO::Handle;
92 use IO::Select;
93 use Crypt::Rijndael;
94 use MIME::Base64;
95 use Digest::MD5 qw(md5 md5_hex md5_base64);
96 use XML::Simple;
97 use Data::Dumper;
98 use Sys::Syslog qw( :DEFAULT setlogsock);
99 use Time::HiRes qw( usleep clock_gettime );
100 use File::Spec;
101 use File::Basename;
102 use File::Find;
103 use File::Copy;
104 use File::Path;
105 use Net::LDAP;
106 use Net::LDAP::Util qw(:escape);
107 use File::Pid;
108 use GOsaSI::GosaSupportDaemon;
110 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
111 use Symbol qw(qualify_to_ref);
112 use Fcntl qw/:flock/;
113 use POSIX;
115 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
117 # revision number of server and program name
118 my $server_headURL;
119 my $server_revision;
120 my $server_status;
122 my $db_module = "DBsqlite";
123 {
124 no strict "refs";
125 require ("GOsaSI/".$db_module.".pm");
126 ("GOsaSI/".$db_module)->import;
127 }
129 my $modules_path = "/usr/lib/gosa-si/modules";
130 use lib "/usr/lib/gosa-si/modules";
132 my ($foreground, $ping_timeout);
133 my ($server);
134 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
135 my ($messaging_db_loop_delay);
136 my $procid;
137 my $arp_fifo;
138 my $debug_parts = 0;
139 my $debug_parts_bitstring;
140 my ($xml);
141 my $sources_list;
142 my $max_clients;
143 my %repo_files=();
144 my $repo_path;
145 my %repo_dirs=();
147 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
149 # Variables declared in config file are always set to 'our'
150 our (%cfg_defaults, $log_file, $pid_file, $pid,
151 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
152 $arp_activ, $gosa_unit_tag,
153 $GosaPackages_key, $gosa_timeout,
154 $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
155 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
156 $arp_enabled, $arp_interface,
157 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
158 $new_systems_ou,
159 $arch,
160 );
162 # additional variable which should be globaly accessable
163 our $server_address;
164 our $server_mac_address;
165 our $gosa_address;
166 our $no_arp;
167 our $forground;
168 our $cfg_file;
169 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
170 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
171 our $known_modules;
172 our $known_functions;
173 our $root_uid;
174 our $adm_gid;
175 our $verbose= 0;
176 our $global_kernel;
178 # where is the config stored by default and his name
179 our $config = '/etc/gosa-si/server.conf';
181 # by default dumping of config is undefined
182 our $dump_config = undef;
184 # if foreground is not null, script will be not forked to background
185 $foreground = 0 ;
187 # specifies the timeout seconds while checking the online status of a registrating client
188 $ping_timeout = 5;
190 $no_arp = 0;
191 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
192 my @packages_list_statements;
193 my $watch_for_new_jobs_in_progress = 0;
195 # holds all incoming decrypted messages
196 our $incoming_db;
197 our $incoming_tn = 'incoming';
198 my $incoming_file_name;
199 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
200 "timestamp VARCHAR(14) DEFAULT 'none'",
201 "headertag VARCHAR(255) DEFAULT 'none'",
202 "targettag VARCHAR(255) DEFAULT 'none'",
203 "xmlmessage TEXT",
204 "module VARCHAR(255) DEFAULT 'none'",
205 "sessionid VARCHAR(255) DEFAULT '0'",
206 );
208 # holds all gosa jobs
209 our $job_db;
210 our $job_queue_tn = 'jobs';
211 my $job_queue_file_name;
212 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
213 "timestamp VARCHAR(14) DEFAULT 'none'",
214 "status VARCHAR(255) DEFAULT 'none'",
215 "result TEXT",
216 "progress VARCHAR(255) DEFAULT 'none'",
217 "headertag VARCHAR(255) DEFAULT 'none'",
218 "targettag VARCHAR(255) DEFAULT 'none'",
219 "xmlmessage TEXT",
220 "macaddress VARCHAR(17) DEFAULT 'none'",
221 "plainname VARCHAR(255) DEFAULT 'none'",
222 "siserver VARCHAR(255) DEFAULT 'none'",
223 "modified INTEGER DEFAULT '0'",
224 "periodic VARCHAR(6) DEFAULT 'none'",
225 );
227 # holds all other gosa-si-server
228 our $known_server_db;
229 our $known_server_tn = "known_server";
230 my $known_server_file_name;
231 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)", "update_time VARCHAR(14)");
233 # holds all registrated clients
234 our $known_clients_db;
235 our $known_clients_tn = "known_clients";
236 my $known_clients_file_name;
237 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
239 # holds all registered clients at a foreign server
240 our $foreign_clients_db;
241 our $foreign_clients_tn = "foreign_clients";
242 my $foreign_clients_file_name;
243 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
245 # holds all logged in user at each client
246 our $login_users_db;
247 our $login_users_tn = "login_users";
248 my $login_users_file_name;
249 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
251 # holds all fai server, the debian release and tag
252 our $fai_server_db;
253 our $fai_server_tn = "fai_server";
254 my $fai_server_file_name;
255 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
257 our $fai_release_db;
258 our $fai_release_tn = "fai_release";
259 my $fai_release_file_name;
260 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
262 # holds all packages available from different repositories
263 our $packages_list_db;
264 our $packages_list_tn = "packages_list";
265 my $packages_list_file_name;
266 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
267 my $outdir = "/tmp/packages_list_db";
269 # holds all messages which should be delivered to a user
270 our $messaging_db;
271 our $messaging_tn = "messaging";
272 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
273 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
274 my $messaging_file_name;
276 # path to directory to store client install log files
277 our $client_fai_log_dir = "/var/log/fai";
279 # queue which stores taskes until one of the $max_children children are ready to process the task
280 #my @tasks = qw();
281 my @msgs_to_decrypt = qw();
282 my $max_children = 2;
285 # loop delay for job queue to look for opsi jobs
286 my $job_queue_opsi_delay = 10;
287 our $opsi_client;
288 our $opsi_url;
290 # Lifetime of logged in user information. If no update information comes after n seconds,
291 # the user is expeceted to be no longer logged in or the host is no longer running. Because
292 # of this, the user is deleted from login_users_db
293 our $logged_in_user_date_of_expiry = 600;
295 # List of month names, used in function daemon_log
296 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
298 # List of accepted periodical xml tags related to cpan modul DateTime
299 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
302 %cfg_defaults = (
303 "General" => {
304 "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
305 "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.pid"],
306 },
307 "Server" => {
308 "ip" => [\$server_ip, "0.0.0.0"],
309 "port" => [\$server_port, "20081"],
310 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
311 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
312 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
313 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
314 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
315 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
316 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
317 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
318 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
319 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
320 "repo-path" => [\$repo_path, '/srv/www/debian'],
321 "debian-arch" => [\$arch, 'i386'],
322 "ldap-uri" => [\$ldap_uri, ""],
323 "ldap-base" => [\$ldap_base, ""],
324 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
325 "ldap-admin-password" => [\$ldap_admin_password, ""],
326 "ldap-version" => [\$ldap_version, 3],
327 "ldap-retry-sec" => [\$ldap_retry_sec, 10],
328 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
329 "max-clients" => [\$max_clients, 10],
330 "wol-password" => [\$wake_on_lan_passwd, ""],
331 "mysql-username" => [\$mysql_username, "gosa_si"],
332 "mysql-password" => [\$mysql_password, ""],
333 "mysql-database" => [\$mysql_database, "gosa_si"],
334 "mysql-host" => [\$mysql_host, "127.0.0.1"],
335 },
336 "GOsaPackages" => {
337 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
338 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
339 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
340 "key" => [\$GosaPackages_key, "none"],
341 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
342 },
343 "ClientPackages" => {
344 "key" => [\$ClientPackages_key, "none"],
345 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
346 },
347 "ServerPackages"=> {
348 "enabled" => [\$serverPackages_enabled, "true"],
349 "address" => [\$foreign_server_string, ""],
350 "dns-lookup" => [\$dns_lookup, "true"],
351 "domain" => [\$server_domain, ""],
352 "key" => [\$ServerPackages_key, "none"],
353 "key-lifetime" => [\$foreign_servers_register_delay, 120],
354 "job-synchronization-enabled" => [\$job_synchronization, "true"],
355 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
356 },
357 "ArpHandler" => {
358 "enabled" => [\$arp_enabled, "false"],
359 "interface" => [\$arp_interface, "all"],
360 },
361 "Opsi" => {
362 "enabled" => [\$opsi_enabled, "false"],
363 "server" => [\$opsi_server, "localhost"],
364 "admin" => [\$opsi_admin, "opsi-admin"],
365 "password" => [\$opsi_password, "secret"],
366 },
368 );
370 #############################
371 #
372 # @brief Display error message and/or help text.
373 #
374 # In correspondence to previous GetOptions
375 #
376 # @param $text - string to print as error message
377 # @param $help - set true, if you want to show usage help
378 #
379 sub usage
380 {
381 my( $text, $help ) = @_;
383 $text = undef if( 'h' eq $text );
384 (defined $text) && print STDERR "\n$text\n";
386 if( (defined $help && $help)
387 || (!defined $help && !defined $text) )
388 {
389 print STDERR << "EOF";
391 usage: $0 [-hvf] [-c config] [-d number]
393 -h : this (help) message
394 -c <file> : config file (default: ${config})
395 -x <cfg> : dump configuration to stdout
396 ( 1 = current, 2 = default )
397 -f : foreground (don't fork)
398 -v : be verbose (multiple to increase verbosity)
399 'v': error logs
400 'vvv': warning plus error logs
401 'vvvvv': info plus warning logs
402 'vvvvvvv': debug plus info logs
403 -no-arp : starts gosa-si-server without connection to arp module
404 -d <int> : if verbose level is higher than 7x 'v' specified parts can be debugged
405 1 : report incoming messages
406 2 : report unencrypted outgoing messages
407 4 : report encrypting key for messages
408 8 : report decrypted incoming message and verification if the message complies gosa-si requirements
409 16 : message processing
410 32 : ldap connectivity
411 64 : database status and connectivity
412 128 : main process
413 256 : creation of packages_list_db
414 512 : ARP debug information
415 EOF
416 }
417 print( "\n" );
419 exit( -1 );
420 }
422 #############################
423 #
424 # @brief Manage gosa-si-client configuration.
425 #
426 # Will exit after successfull dump to stdout (type = 1 | 2)
427 #
428 # Dump type can be:
429 # 1: Current gosa-si-client configuration in config file (exit)
430 # 2: Default gosa-si-client configuration (exit)
431 # 3: Dump to logfile (no exit)
432 #
433 # @param int config type
434 #
435 sub dump_configuration {
437 my( $cfg_type ) = @_;
439 return if( ! defined $cfg_type );
441 if(1==$cfg_type ) {
442 print( "# Current gosa-si-server configuration\n" );
443 } elsif (2==$cfg_type) {
444 print( "# Default gosa-si-server configuration\n" );
445 } elsif (3==$cfg_type) {
446 daemon_log( "Dumping gosa-si-server configuration\n", 2 );
447 } else {
448 return;
449 }
451 foreach my $section (keys %cfg_defaults) {
452 if( 3 != $cfg_type ) {
453 print( "\n[${section}]\n" );
454 } else {
455 daemon_log( "\n [${section}]\n", 3 );
456 }
458 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
459 my $pinfo = $cfg_defaults{ $section }{ $param };
460 my $value;
461 if (1==$cfg_type) {
462 if( defined( ${@$pinfo[ 0 ]} ) ) {
463 $value = ${@$pinfo[ 0 ]};
464 print( "$param=$value\n" );
465 } else {
466 print( "#${param}=\n" );
467 }
468 } elsif (2==$cfg_type) {
469 $value = @{$pinfo}[ 1 ];
470 if( defined( @$pinfo[ 1 ] ) ) {
471 $value = @{$pinfo}[ 1 ];
472 print( "$param=$value\n" );
473 } else {
474 print( "#${param}=\n" );
475 }
476 } elsif (3==$cfg_type) {
477 if( defined( ${@$pinfo[ 0 ]} ) ) {
478 $value = ${@$pinfo[ 0 ]};
479 daemon_log( " $param=$value\n", 3 )
480 }
481 }
482 }
483 }
486 # We just exit at stdout dump
487 if( 3 == $cfg_type ) {
488 daemon_log( "\n", 3 );
489 } else {
490 exit( 0 );
491 }
492 }
495 #=== FUNCTION ================================================================
496 # NAME: logging
497 # PARAMETERS: level - string - default 'info'
498 # msg - string -
499 # facility - string - default 'LOG_DAEMON'
500 # RETURNS: nothing
501 # DESCRIPTION: function for logging
502 #===============================================================================
503 sub daemon_log {
504 my( $msg, $level ) = @_;
505 if (not defined $msg) { return }
506 if (not defined $level) { $level = 1 }
507 my $to_be_logged = 0;
509 # Write log line if line level is lower than verbosity given in commandline
510 if ($level <= $verbose)
511 {
512 $to_be_logged = 1 ;
513 }
515 # Write if debug flag is set and bitstring matches
516 if ($debug_parts > 0)
517 {
518 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
519 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
520 if (int($debug_parts_bitstring & $tmp_level_bitstring))
521 {
522 $to_be_logged = 1;
523 }
524 }
526 if ($to_be_logged)
527 {
528 if(defined $log_file){
529 my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
530 if(not $open_log_fh) {
531 print STDERR "cannot open $log_file: $!";
532 return;
533 }
534 # Check owner and group of log_file and update settings if necessary
535 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
536 if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
537 chown($root_uid, $adm_gid, $log_file);
538 }
540 # Prepare time string for log message
541 my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
542 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
543 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
544 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
545 $month = $monthnames[$month];
546 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
547 $year+=1900;
549 # Microseconds since epoch
550 my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
551 $microSeconds =~ s/^\d*(.\d\d)$/$1/;
553 # Build log message and write it to log file and commandline
554 chomp($msg);
555 my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $0 $msg\n";
556 flock(LOG_HANDLE, LOCK_EX);
557 seek(LOG_HANDLE, 0, 2);
558 print LOG_HANDLE $log_msg;
559 flock(LOG_HANDLE, LOCK_UN);
560 if( $foreground )
561 {
562 print STDERR $log_msg;
563 }
564 close( LOG_HANDLE );
565 }
566 }
567 }
570 #=== FUNCTION ================================================================
571 # NAME: check_cmdline_param
572 # PARAMETERS: nothing
573 # RETURNS: nothing
574 # DESCRIPTION: validates commandline parameter
575 #===============================================================================
576 sub check_cmdline_param () {
577 my $err_counter = 0;
579 # Check configuration file
580 if(not defined($cfg_file)) {
581 $cfg_file = "/etc/gosa-si/server.conf";
582 if(! -r $cfg_file) {
583 print STDERR "Please specify a config file.\n";
584 $err_counter++;
585 }
586 }
588 # Prepare identification which gosa-si parts should be debugged and which not
589 if (defined $debug_parts)
590 {
591 if ($debug_parts =~ /^\d+$/)
592 {
593 $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
594 }
595 else
596 {
597 print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
598 $err_counter++;
599 }
600 }
602 # Exit if an error occour
603 if( $err_counter > 0 ) { &usage( "", 1 ); }
604 }
607 #=== FUNCTION ================================================================
608 # NAME: check_pid
609 # PARAMETERS: nothing
610 # RETURNS: nothing
611 # DESCRIPTION: handels pid processing
612 #===============================================================================
613 sub check_pid {
614 $pid = -1;
615 # Check, if we are already running
616 if( open( my $LOCK_FILE, "<", "$pid_file" ) ) {
617 $pid = <$LOCK_FILE>;
618 if( defined $pid ) {
619 chomp( $pid );
620 if( -f "/proc/$pid/stat" ) {
621 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
622 if( $stat ) {
623 print STDERR "\nERROR: Already running!\n";
624 close( $LOCK_FILE );
625 exit -1;
626 }
627 }
628 }
629 close( $LOCK_FILE );
630 unlink( $pid_file );
631 }
633 # create a syslog msg if it is not to possible to open PID file
634 if (not sysopen(my $LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
635 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
636 if (open(my $LOCK_FILE, '<', $pid_file)
637 && ($pid = <$LOCK_FILE>))
638 {
639 chomp($pid);
640 $msg .= "(PID $pid)\n";
641 } else {
642 $msg .= "(unable to read PID)\n";
643 }
644 if( ! ($foreground) ) {
645 openlog( $0, "cons,pid", "daemon" );
646 syslog( "warning", $msg );
647 closelog();
648 }
649 else {
650 print( STDERR " $msg " );
651 }
652 exit( -1 );
653 }
654 }
656 #=== FUNCTION ================================================================
657 # NAME: import_modules
658 # PARAMETERS: module_path - string - abs. path to the directory the modules
659 # are stored
660 # RETURNS: nothing
661 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
662 # state is on is imported by "require 'file';"
663 #===============================================================================
664 sub import_modules {
665 if (not -e $modules_path) {
666 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
667 }
669 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
671 while (defined (my $file = readdir (DIR))) {
672 if (not $file =~ /(\S*?).pm$/) {
673 next;
674 }
675 my $mod_name = $1;
677 # ArpHandler switch
678 if( $file =~ /ArpHandler.pm/ ) {
679 if( $arp_enabled eq "false" ) { next; }
680 }
682 # ServerPackages switch
683 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false")
684 {
685 $dns_lookup = "false";
686 next;
687 }
689 eval { require $file; };
690 if ($@) {
691 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
692 daemon_log("$@", 1);
693 exit;
694 } else {
695 my $info = eval($mod_name.'::get_module_info()');
696 # Only load module if get_module_info() returns a non-null object
697 if( $info ) {
698 my ($input_address, $input_key, $event_hash) = @{$info};
699 $known_modules->{$mod_name} = $info;
700 daemon_log("0 INFO: module $mod_name loaded", 5);
701 }
702 }
703 }
704 close (DIR);
705 }
707 #=== FUNCTION ================================================================
708 # NAME: password_check
709 # PARAMETERS: nothing
710 # RETURNS: nothing
711 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
712 # the same password
713 #===============================================================================
714 sub password_check {
715 my $passwd_hash = {};
716 while (my ($mod_name, $mod_info) = each %$known_modules) {
717 my $mod_passwd = @$mod_info[1];
718 if (not defined $mod_passwd) { next; }
719 if (not exists $passwd_hash->{$mod_passwd}) {
720 $passwd_hash->{$mod_passwd} = $mod_name;
722 # escalates critical error
723 } else {
724 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
725 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
726 exit( -1 );
727 }
728 }
730 }
733 #=== FUNCTION ================================================================
734 # NAME: sig_int_handler
735 # PARAMETERS: signal - string - signal came from system
736 # RETURNS: nothing
737 # DESCRIPTION: handle tasks to be done before signal becomes active
738 #===============================================================================
739 sub sig_int_handler {
740 my ($signal) = @_;
742 # if (defined($ldap_handle)) {
743 # $ldap_handle->disconnect;
744 # }
745 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
747 daemon_log("shutting down gosa-si-server", 1);
749 $global_kernel->yield(TCP_SERVER => 'shutdown');
751 # to be removed rather crude !!
752 #system("kill `ps -C gosa-si-server -o pid=`");
754 $pid->remove or warn "Could not remove $pid_file\n";
756 exit(0);
757 }
758 $SIG{INT} = \&sig_int_handler;
761 sub check_key_and_xml_validity {
762 my ($crypted_msg, $module_key, $session_id) = @_;
763 my $msg;
764 my $msg_hash;
765 my $error_string;
766 eval{
767 $msg = &decrypt_msg($crypted_msg, $module_key);
769 if ($msg =~ /<xml>/i){
770 $msg =~ s/\s+/ /g; # just for better daemon_log
771 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
772 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
774 ##############
775 # check header
776 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
777 my $header_l = $msg_hash->{'header'};
778 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
779 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
780 my $header = @{$header_l}[0];
781 if( 0 == length $header) { die 'empty string in header tag'; }
783 ##############
784 # check source
785 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
786 my $source_l = $msg_hash->{'source'};
787 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
788 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
789 my $source = @{$source_l}[0];
790 if( 0 == length $source) { die 'source error'; }
792 ##############
793 # check target
794 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
795 my $target_l = $msg_hash->{'target'};
796 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
797 }
798 };
799 if($@) {
800 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
801 $msg = undef;
802 $msg_hash = undef;
803 }
805 return ($msg, $msg_hash);
806 }
809 sub check_outgoing_xml_validity {
810 my ($msg, $session_id) = @_;
812 my $msg_hash;
813 eval{
814 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
816 ##############
817 # check header
818 my $header_l = $msg_hash->{'header'};
819 if( 1 != @{$header_l} ) {
820 die 'no or more than one headers specified';
821 }
822 my $header = @{$header_l}[0];
823 if( 0 == length $header) {
824 die 'header has length 0';
825 }
827 ##############
828 # check source
829 my $source_l = $msg_hash->{'source'};
830 if( 1 != @{$source_l} ) {
831 die 'no or more than 1 sources specified';
832 }
833 my $source = @{$source_l}[0];
834 if( 0 == length $source) {
835 die 'source has length 0';
836 }
838 # Check if source contains hostname instead of ip address
839 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
840 my ($hostname,$port) = split(/:/, $source);
841 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
842 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
843 # Write ip address to $source variable
844 $source = "$ip_address:$port";
845 $msg_hash->{source}[0] = $source ;
846 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/;
847 }
848 }
849 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
850 $source =~ /^GOSA$/i) {
851 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
852 }
854 ##############
855 # check target
856 my $target_l = $msg_hash->{'target'};
857 if( 0 == @{$target_l} ) {
858 die "no targets specified";
859 }
860 foreach my $target (@$target_l) {
861 if( 0 == length $target) {
862 die "target has length 0";
863 }
864 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
865 $target =~ /^GOSA$/i ||
866 $target =~ /^\*$/ ||
867 $target =~ /KNOWN_SERVER/i ||
868 $target =~ /JOBDB/i ||
869 $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 ){
870 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
871 }
872 }
873 };
874 if($@) {
875 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
876 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
877 $msg_hash = undef;
878 }
880 return ($msg, $msg_hash);
881 }
884 sub input_from_known_server {
885 my ($input, $remote_ip, $session_id) = @_ ;
886 my ($msg, $msg_hash, $module);
888 my $sql_statement= "SELECT * FROM known_server";
889 my $query_res = $known_server_db->select_dbentry( $sql_statement );
891 while( my ($hit_num, $hit) = each %{ $query_res } ) {
892 my $host_name = $hit->{hostname};
893 if( not $host_name =~ "^$remote_ip") {
894 next;
895 }
896 my $host_key = $hit->{hostkey};
897 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
898 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
900 # check if module can open msg envelope with module key
901 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
902 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
903 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
904 daemon_log("$@", 14);
905 next;
906 }
907 else {
908 $msg = $tmp_msg;
909 $msg_hash = $tmp_msg_hash;
910 $module = "ServerPackages";
911 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
912 last;
913 }
914 }
916 if( (!$msg) || (!$msg_hash) || (!$module) ) {
917 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
918 }
920 return ($msg, $msg_hash, $module);
921 }
924 sub input_from_known_client {
925 my ($input, $remote_ip, $session_id) = @_ ;
926 my ($msg, $msg_hash, $module);
928 my $sql_statement= "SELECT * FROM known_clients";
929 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
930 while( my ($hit_num, $hit) = each %{ $query_res } ) {
931 my $host_name = $hit->{hostname};
932 if( not $host_name =~ /^$remote_ip/) {
933 next;
934 }
935 my $host_key = $hit->{hostkey};
936 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
937 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
939 # check if module can open msg envelope with module key
940 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
942 if( (!$msg) || (!$msg_hash) ) {
943 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
944 next;
945 }
946 else {
947 $module = "ClientPackages";
948 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
949 last;
950 }
951 }
953 if( (!$msg) || (!$msg_hash) || (!$module) ) {
954 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
955 }
957 return ($msg, $msg_hash, $module);
958 }
961 sub input_from_unknown_host {
962 no strict "refs";
963 my ($input, $session_id) = @_ ;
964 my ($msg, $msg_hash, $module);
965 my $error_string;
967 my %act_modules = %$known_modules;
969 while( my ($mod, $info) = each(%act_modules)) {
971 # check a key exists for this module
972 my $module_key = ${$mod."_key"};
973 if( not defined $module_key ) {
974 if( $mod eq 'ArpHandler' ) {
975 next;
976 }
977 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
978 next;
979 }
980 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
982 # check if module can open msg envelope with module key
983 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
984 if( (not defined $msg) || (not defined $msg_hash) ) {
985 next;
986 } else {
987 $module = $mod;
988 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
989 last;
990 }
991 }
993 if( (!$msg) || (!$msg_hash) || (!$module)) {
994 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
995 }
997 return ($msg, $msg_hash, $module);
998 }
1001 sub create_ciphering {
1002 my ($passwd) = @_;
1003 if((!defined($passwd)) || length($passwd)==0) {
1004 $passwd = "";
1005 }
1006 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
1007 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
1008 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
1009 $my_cipher->set_iv($iv);
1010 return $my_cipher;
1011 }
1014 sub encrypt_msg {
1015 my ($msg, $key) = @_;
1016 my $my_cipher = &create_ciphering($key);
1017 my $len;
1018 {
1019 use bytes;
1020 $len= 16-length($msg)%16;
1021 }
1022 $msg = "\0"x($len).$msg;
1023 $msg = $my_cipher->encrypt($msg);
1024 chomp($msg = &encode_base64($msg));
1025 # there are no newlines allowed inside msg
1026 $msg=~ s/\n//g;
1027 return $msg;
1028 }
1031 sub decrypt_msg {
1033 my ($msg, $key) = @_ ;
1034 $msg = &decode_base64($msg);
1035 my $my_cipher = &create_ciphering($key);
1036 $msg = $my_cipher->decrypt($msg);
1037 $msg =~ s/\0*//g;
1038 return $msg;
1039 }
1042 sub get_encrypt_key {
1043 my ($target) = @_ ;
1044 my $encrypt_key;
1045 my $error = 0;
1047 # target can be in known_server
1048 if( not defined $encrypt_key ) {
1049 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
1050 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1051 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1052 my $host_name = $hit->{hostname};
1053 if( $host_name ne $target ) {
1054 next;
1055 }
1056 $encrypt_key = $hit->{hostkey};
1057 last;
1058 }
1059 }
1061 # target can be in known_client
1062 if( not defined $encrypt_key ) {
1063 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
1064 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1065 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1066 my $host_name = $hit->{hostname};
1067 if( $host_name ne $target ) {
1068 next;
1069 }
1070 $encrypt_key = $hit->{hostkey};
1071 last;
1072 }
1073 }
1075 return $encrypt_key;
1076 }
1079 #=== FUNCTION ================================================================
1080 # NAME: open_socket
1081 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
1082 # [PeerPort] string necessary if port not appended by PeerAddr
1083 # RETURNS: socket IO::Socket::INET
1084 # DESCRIPTION: open a socket to PeerAddr
1085 #===============================================================================
1086 sub open_socket {
1087 my ($PeerAddr, $PeerPort) = @_ ;
1088 if(defined($PeerPort)){
1089 $PeerAddr = $PeerAddr.":".$PeerPort;
1090 }
1091 my $socket;
1092 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1093 Porto => "tcp",
1094 Type => SOCK_STREAM,
1095 Timeout => 5,
1096 );
1097 if(not defined $socket) {
1098 return;
1099 }
1100 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1101 return $socket;
1102 }
1105 sub send_msg_to_target {
1106 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1107 my $error = 0;
1108 my $header;
1109 my $timestamp = &get_time();
1110 my $new_status;
1111 my $act_status;
1112 my ($sql_statement, $res);
1114 if( $msg_header ) {
1115 $header = "'$msg_header'-";
1116 } else {
1117 $header = "";
1118 }
1120 # Memorize own source address
1121 my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1122 $own_source_address .= ":".$server_port;
1124 # Patch 0.0.0.0 source to real address
1125 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1126 # Patch GOSA source to real address and add forward_to_gosa tag
1127 $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1129 # encrypt xml msg
1130 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1132 # opensocket
1133 my $socket = &open_socket($address);
1134 if( !$socket ) {
1135 daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1136 $error++;
1137 }
1139 if( $error == 0 ) {
1140 # send xml msg
1141 print $socket $crypted_msg.";$own_source_address\n";
1142 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1143 daemon_log("$session_id DEBUG: message:\n$msg", 12);
1145 }
1147 # close socket in any case
1148 if( $socket ) {
1149 close $socket;
1150 }
1152 if( $error > 0 ) { $new_status = "down"; }
1153 else { $new_status = $msg_header; }
1156 # known_clients
1157 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1158 $res = $known_clients_db->select_dbentry($sql_statement);
1159 if( keys(%$res) == 1) {
1160 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1161 if ($act_status eq "down" && $new_status eq "down") {
1162 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1163 $res = $known_clients_db->del_dbentry($sql_statement);
1164 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1165 } else {
1166 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1167 $res = $known_clients_db->update_dbentry($sql_statement);
1168 if($new_status eq "down"){
1169 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1170 } else {
1171 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1172 }
1173 }
1174 }
1176 # known_server
1177 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1178 $res = $known_server_db->select_dbentry($sql_statement);
1179 if( keys(%$res) == 1) {
1180 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1181 if ($act_status eq "down" && $new_status eq "down") {
1182 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1183 $res = $known_server_db->del_dbentry($sql_statement);
1184 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1185 }
1186 else {
1187 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1188 $res = $known_server_db->update_dbentry($sql_statement);
1189 if($new_status eq "down"){
1190 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1191 } else {
1192 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1193 }
1194 }
1195 }
1196 return $error;
1197 }
1200 sub update_jobdb_status_for_send_msgs {
1201 my ($session_id, $answer, $error) = @_;
1202 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1203 &daemon_log("$session_id DEBUG: try to update job status", 138);
1204 my $jobdb_id = $1;
1206 $answer =~ /<header>(.*)<\/header>/;
1207 my $job_header = $1;
1209 $answer =~ /<target>(.*)<\/target>/;
1210 my $job_target = $1;
1212 # Sending msg failed
1213 if( $error ) {
1215 # Set jobs to done, jobs do not need to deliver their message in any case
1216 if (($job_header eq "trigger_action_localboot")
1217 ||($job_header eq "trigger_action_lock")
1218 ||($job_header eq "trigger_action_halt")
1219 ) {
1220 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1221 my $res = $job_db->update_dbentry($sql_statement);
1223 # Reactivate jobs, jobs need to deliver their message
1224 } elsif (($job_header eq "trigger_action_activate")
1225 ||($job_header eq "trigger_action_update")
1226 ||($job_header eq "trigger_action_reinstall")
1227 ||($job_header eq "trigger_activate_new")
1228 ) {
1229 if ($job_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) {
1230 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1231 } else {
1232 # If we don't have the mac adress at this time, we use the plainname
1233 my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1234 my $plainname = $job_target;
1235 if ((keys(%$plainname_result) > 0) ) {
1236 $plainname = $plainname_result->{1}->{$job_target};
1237 }
1238 &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1239 }
1240 # For all other messages
1241 } else {
1242 my $sql_statement = "UPDATE $job_queue_tn ".
1243 "SET status='error', result='can not deliver msg, please consult log file' ".
1244 "WHERE id=$jobdb_id";
1245 my $res = $job_db->update_dbentry($sql_statement);
1246 }
1248 # Sending msg was successful
1249 } else {
1250 # Set jobs localboot, lock, activate, halt, reboot and wake to done
1251 # jobs reinstall, update, inst_update do themself setting to done
1252 if (($job_header eq "trigger_action_localboot")
1253 ||($job_header eq "trigger_action_lock")
1254 ||($job_header eq "trigger_action_activate")
1255 ||($job_header eq "trigger_action_halt")
1256 ||($job_header eq "trigger_action_reboot")
1257 ||($job_header eq "trigger_action_wake")
1258 ||($job_header eq "trigger_wake")
1259 ) {
1261 my $sql_statement = "UPDATE $job_queue_tn ".
1262 "SET status='done' ".
1263 "WHERE id=$jobdb_id AND status='processed'";
1264 my $res = $job_db->update_dbentry($sql_statement);
1265 } else {
1266 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138);
1267 }
1268 }
1269 } else {
1270 &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138);
1271 }
1272 }
1274 sub reactivate_job_with_delay {
1275 my ($session_id, $target, $header, $delay) = @_ ;
1276 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1278 if (not defined $delay) { $delay = 30 } ;
1279 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1281 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'";
1282 my $res = $job_db->update_dbentry($sql);
1283 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1284 "cause client '$target' is currently not available", 5);
1285 return;
1286 }
1289 sub sig_handler {
1290 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1291 daemon_log("0 INFO got signal '$signal'", 1);
1292 $kernel->sig_handled();
1293 return;
1294 }
1297 sub msg_to_decrypt {
1298 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1299 my $session_id = $session->ID;
1300 my ($msg, $msg_hash, $module);
1301 my $error = 0;
1303 # fetch new msg out of @msgs_to_decrypt
1304 my $tmp_next_msg = shift @msgs_to_decrypt;
1305 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1307 # msg is from a new client or gosa
1308 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1310 # msg is from a gosa-si-server
1311 if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1312 if (not defined $msg_source)
1313 {
1314 # Only needed, to be compatible with older gosa-si-server versions
1315 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1316 }
1317 else
1318 {
1319 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1320 }
1321 }
1322 # msg is from a gosa-si-client
1323 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1324 if (not defined $msg_source)
1325 {
1326 # Only needed, to be compatible with older gosa-si-server versions
1327 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1328 }
1329 else
1330 {
1331 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1332 }
1333 }
1334 # an error occurred
1335 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1336 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client
1337 # or a server. In case of a client, send a ping. If the client could not understand a msg from its
1338 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1339 # and trigger a re-registering process for servers
1340 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1341 {
1342 daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1343 my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'";
1344 daemon_log("$session_id DEBUG: $update_statement", 7);
1345 my $upadte_res = $known_server_db->exec_statement($update_statement);
1346 $kernel->yield("register_at_foreign_servers");
1347 }
1348 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1349 {
1350 daemon_log("$session_id WARNING: Cannot understand incoming msg from client '$msg_source'. Send ping-msg to cause a re-registering of the client if necessary", 3);
1351 #my $remote_ip = $heap->{'remote_ip'};
1352 #my $remote_port = $heap->{'remote_port'};
1353 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1354 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1355 daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1356 }
1357 else
1358 {
1359 my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1360 daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1361 daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1362 }
1364 $error++
1365 }
1368 my $header;
1369 my $target;
1370 my $source;
1371 my $done = 0;
1372 my $sql;
1373 my $res;
1375 # check whether this message should be processed here
1376 if ($error == 0) {
1377 $header = @{$msg_hash->{'header'}}[0];
1378 $target = @{$msg_hash->{'target'}}[0];
1379 $source = @{$msg_hash->{'source'}}[0];
1380 my $not_found_in_known_clients_db = 0;
1381 my $not_found_in_known_server_db = 0;
1382 my $not_found_in_foreign_clients_db = 0;
1383 my $local_address;
1384 my $local_mac;
1385 my ($target_ip, $target_port) = split(':', $target);
1387 # Determine the local ip address if target is an ip address
1388 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1389 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1390 } else {
1391 $local_address = $server_address;
1392 }
1394 # Determine the local mac address if target is a mac address
1395 if ($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) {
1396 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1397 my $network_interface= &get_interface_for_ip($loc_ip);
1398 $local_mac = &get_mac_for_interface($network_interface);
1399 } else {
1400 $local_mac = $server_mac_address;
1401 }
1403 # target and source is equal to GOSA -> process here
1404 if (not $done) {
1405 if ($target eq "GOSA" && $source eq "GOSA") {
1406 $done = 1;
1407 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1408 }
1409 }
1411 # target is own address without forward_to_gosa-tag -> process here
1412 if (not $done) {
1413 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1414 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1415 $done = 1;
1416 if ($source eq "GOSA") {
1417 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1418 }
1419 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1420 }
1421 }
1423 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1424 if (not $done) {
1425 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1426 my $gosa_at;
1427 my $gosa_session_id;
1428 if (($target eq $local_address) && (defined $forward_to_gosa)){
1429 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1430 if ($gosa_at ne $local_address) {
1431 $done = 1;
1432 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11);
1433 }
1434 }
1435 }
1437 # Target is a client address and there is a processing function within a plugin -> process loaclly
1438 if (not $done)
1439 {
1440 # Check if target is a client address
1441 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1442 $res = $known_clients_db->select_dbentry($sql);
1443 if ((keys(%$res) > 0) )
1444 {
1445 my $hostname = $res->{1}->{'hostname'};
1446 my $reduced_header = $header;
1447 $reduced_header =~ s/gosa_//;
1448 # Check if there is a processing function within a plugin
1449 if (exists $known_functions->{$reduced_header})
1450 {
1451 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1452 $done = 1;
1453 &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1454 }
1455 }
1456 }
1458 # If header has a 'job_' prefix, do always process message locally
1459 # which means put it into job queue
1460 if ((not $done) && ($header =~ /job_/))
1461 {
1462 $done = 1;
1463 &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1464 }
1466 # if message should be processed here -> add message to incoming_db
1467 if ($done) {
1468 # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1469 # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1470 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1471 $module = "GosaPackages";
1472 }
1474 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1475 primkey=>[],
1476 headertag=>$header,
1477 targettag=>$target,
1478 xmlmessage=>&encode_base64($msg),
1479 timestamp=>&get_time,
1480 module=>$module,
1481 sessionid=>$session_id,
1482 } );
1483 $kernel->yield('watch_for_next_tasks');
1484 }
1486 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1487 if (not $done) {
1488 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1489 my $gosa_at;
1490 my $gosa_session_id;
1491 if (($target eq $local_address) && (defined $forward_to_gosa)){
1492 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1493 if ($gosa_at eq $local_address) {
1494 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1495 if( defined $session_reference ) {
1496 $heap = $session_reference->get_heap();
1497 }
1498 if(exists $heap->{'client'}) {
1499 $msg = &encrypt_msg($msg, $GosaPackages_key);
1500 $heap->{'client'}->put($msg);
1501 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11);
1502 }
1503 $done = 1;
1504 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1505 }
1506 }
1508 }
1510 # target is a client address in known_clients -> forward to client
1511 if (not $done) {
1512 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1513 $res = $known_clients_db->select_dbentry($sql);
1514 if (keys(%$res) > 0)
1515 {
1516 $done = 1;
1517 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1518 my $hostkey = $res->{1}->{'hostkey'};
1519 my $hostname = $res->{1}->{'hostname'};
1520 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1521 $msg =~ s/<header>gosa_/<header>/;
1522 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1523 if ($error) {
1524 &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1525 }
1526 }
1527 else
1528 {
1529 $not_found_in_known_clients_db = 1;
1530 }
1531 }
1533 # target is a client address in foreign_clients -> forward to registration server
1534 if (not $done) {
1535 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1536 $res = $foreign_clients_db->select_dbentry($sql);
1537 if (keys(%$res) > 0) {
1538 my $hostname = $res->{1}->{'hostname'};
1539 my ($host_ip, $host_port) = split(/:/, $hostname);
1540 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1541 my $regserver = $res->{1}->{'regserver'};
1542 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1543 my $res = $known_server_db->select_dbentry($sql);
1544 if (keys(%$res) > 0) {
1545 my $regserver_key = $res->{1}->{'hostkey'};
1546 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1547 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1548 if ($source eq "GOSA") {
1549 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1550 }
1551 my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1552 if ($error) {
1553 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1);
1554 }
1555 }
1556 $done = 1;
1557 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1558 } else {
1559 $not_found_in_foreign_clients_db = 1;
1560 }
1561 }
1563 # target is a server address -> forward to server
1564 if (not $done) {
1565 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1566 $res = $known_server_db->select_dbentry($sql);
1567 if (keys(%$res) > 0) {
1568 my $hostkey = $res->{1}->{'hostkey'};
1570 if ($source eq "GOSA") {
1571 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1572 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1574 }
1576 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1577 $done = 1;
1578 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1579 } else {
1580 $not_found_in_known_server_db = 1;
1581 }
1582 }
1585 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1586 if ( $not_found_in_foreign_clients_db
1587 && $not_found_in_known_server_db
1588 && $not_found_in_known_clients_db) {
1589 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process '$header' here", 11);
1590 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1591 $module = "GosaPackages";
1592 }
1593 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1594 primkey=>[],
1595 headertag=>$header,
1596 targettag=>$target,
1597 xmlmessage=>&encode_base64($msg),
1598 timestamp=>&get_time,
1599 module=>$module,
1600 sessionid=>$session_id,
1601 } );
1602 $done = 1;
1603 $kernel->yield('watch_for_next_tasks');
1604 }
1607 if (not $done) {
1608 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1609 if ($source eq "GOSA") {
1610 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1611 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1613 my $session_reference = $kernel->ID_id_to_session($session_id);
1614 if( defined $session_reference ) {
1615 $heap = $session_reference->get_heap();
1616 }
1617 if(exists $heap->{'client'}) {
1618 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1619 $heap->{'client'}->put($error_msg);
1620 }
1621 }
1622 }
1624 }
1626 return;
1627 }
1630 sub next_task {
1631 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1632 my $running_task = POE::Wheel::Run->new(
1633 Program => sub { process_task($session, $heap, $task) },
1634 StdioFilter => POE::Filter::Reference->new(),
1635 StdoutEvent => "task_result",
1636 StderrEvent => "task_debug",
1637 CloseEvent => "task_done",
1638 );
1639 $heap->{task}->{ $running_task->ID } = $running_task;
1640 }
1642 sub handle_task_result {
1643 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1644 my $client_answer = $result->{'answer'};
1645 if( $client_answer =~ s/session_id=(\d+)$// ) {
1646 my $session_id = $1;
1647 if( defined $session_id ) {
1648 my $session_reference = $kernel->ID_id_to_session($session_id);
1649 if( defined $session_reference ) {
1650 $heap = $session_reference->get_heap();
1651 }
1652 }
1654 if(exists $heap->{'client'}) {
1655 $heap->{'client'}->put($client_answer);
1656 }
1657 }
1658 $kernel->sig(CHLD => "child_reap");
1659 }
1661 sub handle_task_debug {
1662 my $result = $_[ARG0];
1663 print STDERR "$result\n";
1664 }
1666 sub handle_task_done {
1667 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1668 delete $heap->{task}->{$task_id};
1669 if (exists $heap->{ldap_handle}->{$task_id}) {
1670 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1671 }
1672 }
1674 sub process_task {
1675 no strict "refs";
1676 #CHECK: Not @_[...]?
1677 my ($session, $heap, $task) = @_;
1678 my $error = 0;
1679 my $answer_l;
1680 my ($answer_header, @answer_target_l, $answer_source);
1681 my $client_answer = "";
1683 # prepare all variables needed to process message
1684 #my $msg = $task->{'xmlmessage'};
1685 my $msg = &decode_base64($task->{'xmlmessage'});
1686 my $incoming_id = $task->{'id'};
1687 my $module = $task->{'module'};
1688 my $header = $task->{'headertag'};
1689 my $session_id = $task->{'sessionid'};
1690 my $msg_hash;
1691 eval {
1692 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1693 };
1694 daemon_log("ERROR: XML failure '$@'") if ($@);
1695 my $source = @{$msg_hash->{'source'}}[0];
1697 # set timestamp of incoming client uptodate, so client will not
1698 # be deleted from known_clients because of expiration
1699 my $cur_time = &get_time();
1700 my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'";
1701 my $res = $known_clients_db->exec_statement($sql);
1703 ######################
1704 # process incoming msg
1705 if( $error == 0) {
1706 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1707 daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1708 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1710 if ( 0 < @{$answer_l} ) {
1711 my $answer_str = join("\n", @{$answer_l});
1712 my @headers;
1713 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1714 push(@headers, $1);
1715 }
1716 daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1717 } else {
1718 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1719 }
1721 }
1722 if( !$answer_l ) { $error++ };
1724 ########
1725 # answer
1726 if( $error == 0 ) {
1728 foreach my $answer ( @{$answer_l} ) {
1729 # check outgoing msg to xml validity
1730 my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1731 if( not defined $answer_hash ) { next; }
1733 $answer_header = @{$answer_hash->{'header'}}[0];
1734 @answer_target_l = @{$answer_hash->{'target'}};
1735 $answer_source = @{$answer_hash->{'source'}}[0];
1737 # deliver msg to all targets
1738 foreach my $answer_target ( @answer_target_l ) {
1740 # targets of msg are all gosa-si-clients in known_clients_db
1741 if( $answer_target eq "*" ) {
1742 # answer is for all clients
1743 my $sql_statement= "SELECT * FROM known_clients";
1744 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1745 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1746 my $host_name = $hit->{hostname};
1747 my $host_key = $hit->{hostkey};
1748 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1749 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1750 }
1751 }
1753 # targets of msg are all gosa-si-server in known_server_db
1754 elsif( $answer_target eq "KNOWN_SERVER" ) {
1755 # answer is for all server in known_server
1756 my $sql_statement= "SELECT * FROM $known_server_tn";
1757 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1758 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1759 my $host_name = $hit->{hostname};
1760 my $host_key = $hit->{hostkey};
1761 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1762 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1763 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1764 }
1765 }
1767 # target of msg is GOsa
1768 elsif( $answer_target eq "GOSA" ) {
1769 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1770 my $add_on = "";
1771 if( defined $session_id ) {
1772 $add_on = ".session_id=$session_id";
1773 }
1774 my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1775 daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1776 daemon_log("$session_id DEBUG: message:\n$answer", 12);
1777 # answer is for GOSA and has to returned to connected client
1778 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1779 $client_answer = $gosa_answer.$add_on;
1780 }
1782 # target of msg is job queue at this host
1783 elsif( $answer_target eq "JOBDB") {
1784 $answer =~ /<header>(\S+)<\/header>/;
1785 my $header;
1786 if( defined $1 ) { $header = $1; }
1787 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1788 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1789 }
1791 # Target of msg is a mac address
1792 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 ) {
1793 daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1795 # Looking for macaddress in known_clients
1796 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1797 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1798 my $found_ip_flag = 0;
1799 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1800 my $host_name = $hit->{hostname};
1801 my $host_key = $hit->{hostkey};
1802 $answer =~ s/$answer_target/$host_name/g;
1803 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1804 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1805 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1806 $found_ip_flag++ ;
1807 }
1809 # Looking for macaddress in foreign_clients
1810 if ($found_ip_flag == 0) {
1811 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1812 my $res = $foreign_clients_db->select_dbentry($sql);
1813 while( my ($hit_num, $hit) = each %{ $res } ) {
1814 my $host_name = $hit->{hostname};
1815 my $reg_server = $hit->{regserver};
1816 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1818 # Fetch key for reg_server
1819 my $reg_server_key;
1820 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1821 my $res = $known_server_db->select_dbentry($sql);
1822 if (exists $res->{1}) {
1823 $reg_server_key = $res->{1}->{'hostkey'};
1824 } else {
1825 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1826 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1827 $reg_server_key = undef;
1828 }
1830 # Send answer to server where client is registered
1831 if (defined $reg_server_key) {
1832 $answer =~ s/$answer_target/$host_name/g;
1833 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1834 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1835 $found_ip_flag++ ;
1836 }
1837 }
1838 }
1840 # No mac to ip matching found
1841 if( $found_ip_flag == 0) {
1842 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1843 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1844 }
1846 # Answer is for one specific host
1847 } else {
1848 # get encrypt_key
1849 my $encrypt_key = &get_encrypt_key($answer_target);
1850 if( not defined $encrypt_key ) {
1851 # unknown target
1852 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1853 next;
1854 }
1855 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1856 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1857 }
1858 }
1859 }
1860 }
1862 my $filter = POE::Filter::Reference->new();
1863 my %result = (
1864 status => "seems ok to me",
1865 answer => $client_answer,
1866 );
1868 my $output = $filter->put( [ \%result ] );
1869 print @$output;
1872 }
1874 sub session_start {
1875 my ($kernel) = $_[KERNEL];
1876 $global_kernel = $kernel;
1877 $kernel->yield('register_at_foreign_servers');
1878 $kernel->yield('create_fai_server_db', $fai_server_tn );
1879 $kernel->yield('create_fai_release_db', $fai_release_tn );
1880 $kernel->sig(USR1 => "sig_handler");
1881 $kernel->sig(USR2 => "recreate_packages_db");
1882 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1883 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1884 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1885 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1886 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1887 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1888 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1890 # Start opsi check
1891 if ($opsi_enabled eq "true") {
1892 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1893 }
1895 }
1898 sub watch_for_done_jobs {
1899 my $kernel = $_[KERNEL];
1901 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1902 my $res = $job_db->select_dbentry( $sql_statement );
1904 while( my ($number, $hit) = each %{$res} )
1905 {
1906 # Non periodical jobs can be deleted.
1907 if ($hit->{periodic} eq "none")
1908 {
1909 my $jobdb_id = $hit->{id};
1910 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1911 my $res = $job_db->del_dbentry($sql_statement);
1912 }
1914 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1915 else
1916 {
1917 my ($p_time, $periodic) = split("_", $hit->{periodic});
1918 my $reactivated_ts = $hit->{timestamp};
1919 my $act_ts = int(&get_time());
1920 while ($act_ts > int($reactivated_ts)) # Redo calculation to avoid multiple jobs in the past
1921 {
1922 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1923 }
1924 my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'";
1925 my $res = $job_db->exec_statement($sql);
1926 &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1927 }
1928 }
1930 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1931 }
1934 sub watch_for_opsi_jobs {
1935 my ($kernel) = $_[KERNEL];
1936 # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a
1937 # opsi install job is to parse the xml message. There is still the correct header.
1938 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1939 my $res = $job_db->select_dbentry( $sql_statement );
1941 # Ask OPSI for an update of the running jobs
1942 while (my ($id, $hit) = each %$res ) {
1943 # Determine current parameters of the job
1944 my $hostId = $hit->{'plainname'};
1945 my $macaddress = $hit->{'macaddress'};
1946 my $progress = $hit->{'progress'};
1948 my $result= {};
1950 # For hosts, only return the products that are or get installed
1951 my $callobj;
1952 $callobj = {
1953 method => 'getProductStates_hash',
1954 params => [ $hostId ],
1955 id => 1,
1956 };
1958 my $hres = $opsi_client->call($opsi_url, $callobj);
1959 # TODO : move all opsi relevant statments to opsi plugin
1960 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1961 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1962 #if (not $err) {
1963 # my $htmp = $res->{$hostId};
1964 #
1965 if (not &check_opsi_res($hres)) {
1966 my $htmp= $hres->result->{$hostId};
1967 # Check state != not_installed or action == setup -> load and add
1968 my $products= 0;
1969 my $installed= 0;
1970 my $installing = 0;
1971 my $error= 0;
1972 my @installed_list;
1973 my @error_list;
1974 my $act_status = "none";
1975 foreach my $product (@{$htmp}){
1977 if ($product->{'installationStatus'} ne "not_installed" or
1978 $product->{'actionRequest'} eq "setup"){
1980 # Increase number of products for this host
1981 $products++;
1983 if ($product->{'installationStatus'} eq "failed"){
1984 $result->{$product->{'productId'}}= "error";
1985 unshift(@error_list, $product->{'productId'});
1986 $error++;
1987 }
1988 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1989 $result->{$product->{'productId'}}= "installed";
1990 unshift(@installed_list, $product->{'productId'});
1991 $installed++;
1992 }
1993 if ($product->{'installationStatus'} eq "installing"){
1994 $result->{$product->{'productId'}}= "installing";
1995 $installing++;
1996 $act_status = "installing - ".$product->{'productId'};
1997 }
1998 }
1999 }
2001 # Estimate "rough" progress, avoid division by zero
2002 if ($products == 0) {
2003 $result->{'progress'}= 0;
2004 } else {
2005 $result->{'progress'}= int($installed * 100 / $products);
2006 }
2008 # Set updates in job queue
2009 if ((not $error) && (not $installing) && ($installed)) {
2010 $act_status = "installed - ".join(", ", @installed_list);
2011 }
2012 if ($error) {
2013 $act_status = "error - ".join(", ", @error_list);
2014 }
2015 if ($progress ne $result->{'progress'} ) {
2016 # Updating progress and result
2017 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2018 my $update_res = $job_db->update_dbentry($update_statement);
2019 }
2020 if ($progress eq 100) {
2021 # Updateing status
2022 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
2023 if ($error) {
2024 $done_statement .= "status='error'";
2025 } else {
2026 $done_statement .= "status='done'";
2027 }
2028 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2029 my $done_res = $job_db->update_dbentry($done_statement);
2030 }
2033 }
2034 }
2036 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
2037 }
2040 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
2041 sub watch_for_modified_jobs {
2042 my ($kernel,$heap) = @_[KERNEL, HEAP];
2044 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
2045 my $res = $job_db->select_dbentry( $sql_statement );
2047 # if db contains no jobs which should be update, do nothing
2048 if (keys %$res != 0) {
2050 if ($job_synchronization eq "true") {
2051 # make out of the db result a gosa-si message
2052 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
2054 # update all other SI-server
2055 &inform_all_other_si_server($update_msg);
2056 }
2058 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
2059 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
2060 $res = $job_db->update_dbentry($sql_statement);
2061 }
2063 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
2064 }
2067 sub watch_for_new_jobs {
2068 if($watch_for_new_jobs_in_progress == 0) {
2069 $watch_for_new_jobs_in_progress = 1;
2070 my ($kernel,$heap) = @_[KERNEL, HEAP];
2072 # check gosa job queue for jobs with executable timestamp
2073 my $timestamp = &get_time();
2074 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
2075 my $res = $job_db->exec_statement( $sql_statement );
2077 # Merge all new jobs that would do the same actions
2078 my @drops;
2079 my $hits;
2080 foreach my $hit (reverse @{$res} ) {
2081 my $macaddress= lc @{$hit}[8];
2082 my $headertag= @{$hit}[5];
2083 if(
2084 defined($hits->{$macaddress}) &&
2085 defined($hits->{$macaddress}->{$headertag}) &&
2086 defined($hits->{$macaddress}->{$headertag}[0])
2087 ) {
2088 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2089 }
2090 $hits->{$macaddress}->{$headertag}= $hit;
2091 }
2093 # Delete new jobs with a matching job in state 'processing'
2094 foreach my $macaddress (keys %{$hits}) {
2095 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2096 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2097 if(defined($jobdb_id)) {
2098 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2099 my $res = $job_db->exec_statement( $sql_statement );
2100 foreach my $hit (@{$res}) {
2101 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2102 }
2103 } else {
2104 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2105 }
2106 }
2107 }
2109 # Commit deletion
2110 $job_db->exec_statementlist(\@drops);
2112 # Look for new jobs that could be executed
2113 foreach my $macaddress (keys %{$hits}) {
2115 # Look if there is an executing job
2116 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2117 my $res = $job_db->exec_statement( $sql_statement );
2119 # Skip new jobs for host if there is a processing job
2120 if(defined($res) and defined @{$res}[0]) {
2121 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2122 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2123 if(@{$row}[5] eq 'trigger_action_reinstall') {
2124 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
2125 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2126 if(defined($res_2) and defined @{$res_2}[0]) {
2127 # Set status from goto-activation to 'waiting' and update timestamp
2128 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting', timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
2129 }
2130 }
2131 next;
2132 }
2134 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2135 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2136 if(defined($jobdb_id)) {
2137 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2139 daemon_log("J DEBUG: its time to execute $job_msg", 7);
2140 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2141 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2143 # expect macaddress is unique!!!!!!
2144 my $target = $res_hash->{1}->{hostname};
2146 # change header
2147 $job_msg =~ s/<header>job_/<header>gosa_/;
2149 # add sqlite_id
2150 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2152 $job_msg =~ /<header>(\S+)<\/header>/;
2153 my $header = $1 ;
2154 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
2156 # update status in job queue to ...
2157 # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2158 if (($header =~ /gosa_trigger_action_reinstall/)
2159 || ($header =~ /gosa_trigger_activate_new/)
2160 || ($header =~ /gosa_trigger_action_update/)) {
2161 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2162 my $dbres = $job_db->update_dbentry($sql_statement);
2163 }
2165 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2166 else {
2167 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2168 my $dbres = $job_db->exec_statement($sql_statement);
2169 }
2172 # We don't want parallel processing
2173 last;
2174 }
2175 }
2176 }
2178 $watch_for_new_jobs_in_progress = 0;
2179 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2180 }
2181 }
2184 sub watch_for_new_messages {
2185 my ($kernel,$heap) = @_[KERNEL, HEAP];
2186 my @coll_user_msg; # collection list of outgoing messages
2188 # check messaging_db for new incoming messages with executable timestamp
2189 my $timestamp = &get_time();
2190 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2191 my $res = $messaging_db->exec_statement( $sql_statement );
2192 foreach my $hit (@{$res}) {
2194 # create outgoing messages
2195 my $message_to = @{$hit}[3];
2196 # translate message_to to plain login name
2197 my @message_to_l = split(/,/, $message_to);
2198 my %receiver_h;
2199 foreach my $receiver (@message_to_l) {
2200 if ($receiver =~ /^u_([\s\S]*)$/) {
2201 $receiver_h{$1} = 0;
2202 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2203 my $group_name = $1;
2204 # fetch all group members from ldap and add them to receiver hash
2205 my $ldap_handle = &get_ldap_handle();
2206 if (defined $ldap_handle) {
2207 my $mesg = $ldap_handle->search(
2208 base => $ldap_base,
2209 scope => 'sub',
2210 attrs => ['memberUid'],
2211 filter => "cn=$group_name",
2212 );
2213 if ($mesg->count) {
2214 my @entries = $mesg->entries;
2215 foreach my $entry (@entries) {
2216 my @receivers= $entry->get_value("memberUid");
2217 foreach my $receiver (@receivers) {
2218 $receiver_h{$receiver} = 0;
2219 }
2220 }
2221 }
2222 # translating errors ?
2223 if ($mesg->code) {
2224 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2225 }
2226 &release_ldap_handle($ldap_handle);
2227 # ldap handle error ?
2228 } else {
2229 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2230 }
2231 } else {
2232 my $sbjct = &encode_base64(@{$hit}[1]);
2233 my $msg = &encode_base64(@{$hit}[7]);
2234 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
2235 }
2236 }
2237 my @receiver_l = keys(%receiver_h);
2239 my $message_id = @{$hit}[0];
2241 #add each outgoing msg to messaging_db
2242 my $receiver;
2243 foreach $receiver (@receiver_l) {
2244 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2245 "VALUES ('".
2246 $message_id."', '". # id
2247 @{$hit}[1]."', '". # subject
2248 @{$hit}[2]."', '". # message_from
2249 $receiver."', '". # message_to
2250 "none"."', '". # flag
2251 "out"."', '". # direction
2252 @{$hit}[6]."', '". # delivery_time
2253 @{$hit}[7]."', '". # message
2254 $timestamp."'". # timestamp
2255 ")";
2256 &daemon_log("M DEBUG: $sql_statement", 1);
2257 my $res = $messaging_db->exec_statement($sql_statement);
2258 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2259 }
2261 # set incoming message to flag d=deliverd
2262 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
2263 &daemon_log("M DEBUG: $sql_statement", 7);
2264 $res = $messaging_db->update_dbentry($sql_statement);
2265 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2266 }
2268 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
2269 return;
2270 }
2272 sub watch_for_delivery_messages {
2273 my ($kernel, $heap) = @_[KERNEL, HEAP];
2275 # select outgoing messages
2276 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2277 my $res = $messaging_db->exec_statement( $sql_statement );
2279 # build out msg for each usr
2280 foreach my $hit (@{$res}) {
2281 my $receiver = @{$hit}[3];
2282 my $msg_id = @{$hit}[0];
2283 my $subject = @{$hit}[1];
2284 my $message = @{$hit}[7];
2286 # resolve usr -> host where usr is logged in
2287 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
2288 my $res = $login_users_db->exec_statement($sql);
2290 # receiver is logged in nowhere
2291 if (not ref(@$res[0]) eq "ARRAY") { next; }
2293 # receiver ist logged in at a client registered at local server
2294 my $send_succeed = 0;
2295 foreach my $hit (@$res) {
2296 my $receiver_host = @$hit[0];
2297 my $delivered2host = 0;
2298 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2300 # Looking for host in know_clients_db
2301 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2302 my $res = $known_clients_db->exec_statement($sql);
2304 # Host is known in known_clients_db
2305 if (ref(@$res[0]) eq "ARRAY") {
2306 my $receiver_key = @{@{$res}[0]}[2];
2307 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2308 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2309 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2310 if ($error == 0 ) {
2311 $send_succeed++ ;
2312 $delivered2host++ ;
2313 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2314 } else {
2315 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2316 }
2317 }
2319 # Message already send, do not need to do anything more, otherwise ...
2320 if ($delivered2host) { next;}
2322 # ...looking for host in foreign_clients_db
2323 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2324 $res = $foreign_clients_db->exec_statement($sql);
2326 # Host is known in foreign_clients_db
2327 if (ref(@$res[0]) eq "ARRAY") {
2328 my $registration_server = @{@{$res}[0]}[2];
2330 # Fetch encryption key for registration server
2331 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2332 my $res = $known_server_db->exec_statement($sql);
2333 if (ref(@$res[0]) eq "ARRAY") {
2334 my $registration_server_key = @{@{$res}[0]}[3];
2335 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2336 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2337 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2338 if ($error == 0 ) {
2339 $send_succeed++ ;
2340 $delivered2host++ ;
2341 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2342 } else {
2343 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2344 }
2346 } else {
2347 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2348 "registrated at server '$registration_server', ".
2349 "but no data available in known_server_db ", 1);
2350 }
2351 }
2353 if (not $delivered2host) {
2354 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2355 }
2356 }
2358 if ($send_succeed) {
2359 # set outgoing msg at db to deliverd
2360 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2361 my $res = $messaging_db->exec_statement($sql);
2362 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2363 } else {
2364 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2365 }
2366 }
2368 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2369 return;
2370 }
2373 sub watch_for_done_messages {
2374 my ($kernel,$heap) = @_[KERNEL, HEAP];
2376 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2377 my $res = $messaging_db->exec_statement($sql);
2379 foreach my $hit (@{$res}) {
2380 my $msg_id = @{$hit}[0];
2382 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2383 my $res = $messaging_db->exec_statement($sql);
2385 # not all usr msgs have been seen till now
2386 if ( ref(@$res[0]) eq "ARRAY") { next; }
2388 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2389 $res = $messaging_db->exec_statement($sql);
2391 }
2393 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2394 return;
2395 }
2398 sub watch_for_old_known_clients {
2399 my ($kernel,$heap) = @_[KERNEL, HEAP];
2401 my $sql_statement = "SELECT * FROM $known_clients_tn";
2402 my $res = $known_clients_db->select_dbentry( $sql_statement );
2404 my $cur_time = int(&get_time());
2406 while ( my ($hit_num, $hit) = each %$res) {
2407 my $expired_timestamp = int($hit->{'timestamp'});
2408 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2409 my $dt = DateTime->new( year => $1,
2410 month => $2,
2411 day => $3,
2412 hour => $4,
2413 minute => $5,
2414 second => $6,
2415 );
2417 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2418 $expired_timestamp = $dt->ymd('').$dt->hms('');
2419 if ($cur_time > $expired_timestamp) {
2420 my $hostname = $hit->{'hostname'};
2421 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2422 my $del_res = $known_clients_db->exec_statement($del_sql);
2424 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2425 }
2427 }
2429 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2430 }
2433 sub watch_for_next_tasks {
2434 my ($kernel,$heap) = @_[KERNEL, HEAP];
2436 my $sql = "SELECT * FROM $incoming_tn";
2437 my $res = $incoming_db->select_dbentry($sql);
2439 while ( my ($hit_num, $hit) = each %$res) {
2440 my $headertag = $hit->{'headertag'};
2441 if ($headertag =~ /^answer_(\d+)/) {
2442 # do not start processing, this message is for a still running POE::Wheel
2443 next;
2444 }
2445 my $message_id = $hit->{'id'};
2446 my $session_id = $hit->{'sessionid'};
2447 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2449 $kernel->yield('next_task', $hit);
2451 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2452 my $res = $incoming_db->exec_statement($sql);
2453 }
2454 }
2457 sub get_ldap_handle {
2458 my ($session_id) = @_;
2459 my $heap;
2461 if (not defined $session_id ) { $session_id = 0 };
2462 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2464 my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2465 my $caller_text = "subroutine $subroutine";
2466 if ($subroutine eq "(eval)") {
2467 $caller_text = "eval block within file '$file' for '$evalText'";
2468 }
2469 daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2471 get_handle:
2472 my $ldap_handle = Net::LDAP->new( $ldap_uri );
2473 if (not ref $ldap_handle) {
2474 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1);
2475 sleep($ldap_retry_sec);
2476 goto get_handle;
2477 } else {
2478 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2479 }
2481 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or &daemon_log("$session_id ERROR: Could not bind as '$ldap_admin_dn' to LDAP URI '$ldap_uri'!", 1);
2482 return $ldap_handle;
2483 }
2486 sub release_ldap_handle {
2487 my ($ldap_handle, $session_id) = @_ ;
2488 if (not defined $session_id ) { $session_id = 0 };
2490 if(ref $ldap_handle) {
2491 $ldap_handle->disconnect();
2492 }
2493 &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2494 return;
2495 }
2498 sub change_fai_state {
2499 my ($st, $targets, $session_id) = @_;
2500 $session_id = 0 if not defined $session_id;
2501 # Set FAI state to localboot
2502 my %mapActions= (
2503 reboot => '',
2504 update => 'softupdate',
2505 localboot => 'localboot',
2506 reinstall => 'install',
2507 rescan => '',
2508 wake => '',
2509 memcheck => 'memcheck',
2510 sysinfo => 'sysinfo',
2511 install => 'install',
2512 );
2514 # Return if this is unknown
2515 if (!exists $mapActions{ $st }){
2516 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2517 return;
2518 }
2520 my $state= $mapActions{ $st };
2522 # Build search filter for hosts
2523 my $search= "(&(objectClass=GOhard)";
2524 foreach (@{$targets}){
2525 $search.= "(macAddress=$_)";
2526 }
2527 $search.= ")";
2529 # If there's any host inside of the search string, procress them
2530 if (!($search =~ /macAddress/)){
2531 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2532 return;
2533 }
2535 my $ldap_handle = &get_ldap_handle($session_id);
2536 # Perform search for Unit Tag
2537 my $mesg = $ldap_handle->search(
2538 base => $ldap_base,
2539 scope => 'sub',
2540 attrs => ['dn', 'FAIstate', 'objectClass'],
2541 filter => "$search"
2542 );
2544 if ($mesg->count) {
2545 my @entries = $mesg->entries;
2546 if (0 == @entries) {
2547 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2548 }
2550 foreach my $entry (@entries) {
2551 # Only modify entry if it is not set to '$state'
2552 if ($entry->get_value("FAIstate") ne "$state"){
2553 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2554 my $result;
2555 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2556 if (exists $tmp{'FAIobject'}){
2557 if ($state eq ''){
2558 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2559 } else {
2560 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2561 }
2562 } elsif ($state ne ''){
2563 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2564 }
2566 # Errors?
2567 if ($result->code){
2568 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2569 }
2570 } else {
2571 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42);
2572 }
2573 }
2574 } else {
2575 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2576 }
2577 &release_ldap_handle($ldap_handle, $session_id);
2579 return;
2580 }
2583 sub change_goto_state {
2584 my ($st, $targets, $session_id) = @_;
2585 $session_id = 0 if not defined $session_id;
2587 # Switch on or off?
2588 my $state= $st eq 'active' ? 'active': 'locked';
2590 my $ldap_handle = &get_ldap_handle($session_id);
2591 if( defined($ldap_handle) ) {
2593 # Build search filter for hosts
2594 my $search= "(&(objectClass=GOhard)";
2595 foreach (@{$targets}){
2596 $search.= "(macAddress=$_)";
2597 }
2598 $search.= ")";
2600 # If there's any host inside of the search string, procress them
2601 if (!($search =~ /macAddress/)){
2602 &release_ldap_handle($ldap_handle);
2603 return;
2604 }
2606 # Perform search for Unit Tag
2607 my $mesg = $ldap_handle->search(
2608 base => $ldap_base,
2609 scope => 'sub',
2610 attrs => ['dn', 'gotoMode'],
2611 filter => "$search"
2612 );
2614 if ($mesg->count) {
2615 my @entries = $mesg->entries;
2616 foreach my $entry (@entries) {
2618 # Only modify entry if it is not set to '$state'
2619 if ($entry->get_value("gotoMode") ne $state){
2621 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2622 my $result;
2623 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2625 # Errors?
2626 if ($result->code){
2627 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2628 }
2630 }
2631 }
2632 } else {
2633 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2634 }
2636 }
2637 &release_ldap_handle($ldap_handle, $session_id);
2638 return;
2639 }
2642 sub run_recreate_packages_db {
2643 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2644 my $session_id = $session->ID;
2645 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2646 $kernel->yield('create_fai_release_db', $fai_release_tn);
2647 $kernel->yield('create_fai_server_db', $fai_server_tn);
2648 return;
2649 }
2652 sub run_create_fai_server_db {
2653 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2654 my $session_id = $session->ID;
2655 my $task = POE::Wheel::Run->new(
2656 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2657 StdoutEvent => "session_run_result",
2658 StderrEvent => "session_run_debug",
2659 CloseEvent => "session_run_done",
2660 );
2662 $heap->{task}->{ $task->ID } = $task;
2663 return;
2664 }
2667 sub create_fai_server_db {
2668 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2669 my $result;
2671 if (not defined $session_id) { $session_id = 0; }
2672 my $ldap_handle = &get_ldap_handle($session_id);
2673 if(defined($ldap_handle)) {
2674 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2675 my $mesg= $ldap_handle->search(
2676 base => $ldap_base,
2677 scope => 'sub',
2678 attrs => ['FAIrepository', 'gosaUnitTag'],
2679 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2680 );
2681 if($mesg->{'resultCode'} == 0 &&
2682 $mesg->count != 0) {
2683 foreach my $entry (@{$mesg->{entries}}) {
2684 if($entry->exists('FAIrepository')) {
2685 # Add an entry for each Repository configured for server
2686 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2687 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2688 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2689 $result= $fai_server_db->add_dbentry( {
2690 table => $table_name,
2691 primkey => ['server', 'fai_release', 'tag'],
2692 server => $tmp_url,
2693 fai_release => $tmp_release,
2694 sections => $tmp_sections,
2695 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2696 } );
2697 }
2698 }
2699 }
2700 }
2701 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2702 &release_ldap_handle($ldap_handle);
2704 # TODO: Find a way to post the 'create_packages_list_db' event
2705 if(not defined($dont_create_packages_list)) {
2706 &create_packages_list_db(undef, $session_id);
2707 }
2708 }
2710 return $result;
2711 }
2714 sub run_create_fai_release_db {
2715 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2716 my $session_id = $session->ID;
2717 my $task = POE::Wheel::Run->new(
2718 Program => sub { &create_fai_release_db($table_name, $session_id) },
2719 StdoutEvent => "session_run_result",
2720 StderrEvent => "session_run_debug",
2721 CloseEvent => "session_run_done",
2722 );
2724 $heap->{task}->{ $task->ID } = $task;
2725 return;
2726 }
2729 sub create_fai_release_db {
2730 my ($table_name, $session_id) = @_;
2731 my $result;
2733 # used for logging
2734 if (not defined $session_id) { $session_id = 0; }
2736 my $ldap_handle = &get_ldap_handle($session_id);
2737 if(defined($ldap_handle)) {
2738 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2739 my $mesg= $ldap_handle->search(
2740 base => $ldap_base,
2741 scope => 'sub',
2742 attrs => [],
2743 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2744 );
2745 if(($mesg->code == 0) && ($mesg->count != 0))
2746 {
2747 daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2749 # Walk through all possible FAI container ou's
2750 my @sql_list;
2751 my $timestamp= &get_time();
2752 foreach my $ou (@{$mesg->{entries}}) {
2753 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2754 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2755 my @tmp_array=get_fai_release_entries($tmp_classes);
2756 if(@tmp_array) {
2757 foreach my $entry (@tmp_array) {
2758 if(defined($entry) && ref($entry) eq 'HASH') {
2759 my $sql=
2760 "INSERT INTO $table_name "
2761 ."(timestamp, fai_release, class, type, state) VALUES ("
2762 .$timestamp.","
2763 ."'".$entry->{'release'}."',"
2764 ."'".$entry->{'class'}."',"
2765 ."'".$entry->{'type'}."',"
2766 ."'".$entry->{'state'}."')";
2767 push @sql_list, $sql;
2768 }
2769 }
2770 }
2771 }
2772 }
2774 daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2775 &release_ldap_handle($ldap_handle);
2776 if(@sql_list) {
2777 unshift @sql_list, "VACUUM";
2778 unshift @sql_list, "DELETE FROM $table_name";
2779 $fai_release_db->exec_statementlist(\@sql_list);
2780 }
2781 daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2782 } else {
2783 daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2784 }
2785 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2786 }
2787 return $result;
2788 }
2790 sub get_fai_types {
2791 my $tmp_classes = shift || return undef;
2792 my @result;
2794 foreach my $type(keys %{$tmp_classes}) {
2795 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2796 my $entry = {
2797 type => $type,
2798 state => $tmp_classes->{$type}[0],
2799 };
2800 push @result, $entry;
2801 }
2802 }
2804 return @result;
2805 }
2807 sub get_fai_state {
2808 my $result = "";
2809 my $tmp_classes = shift || return $result;
2811 foreach my $type(keys %{$tmp_classes}) {
2812 if(defined($tmp_classes->{$type}[0])) {
2813 $result = $tmp_classes->{$type}[0];
2815 # State is equal for all types in class
2816 last;
2817 }
2818 }
2820 return $result;
2821 }
2823 sub resolve_fai_classes {
2824 my ($fai_base, $ldap_handle, $session_id) = @_;
2825 if (not defined $session_id) { $session_id = 0; }
2826 my $result;
2827 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2828 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2829 my $fai_classes;
2831 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2832 my $mesg= $ldap_handle->search(
2833 base => $fai_base,
2834 scope => 'sub',
2835 attrs => ['cn','objectClass','FAIstate'],
2836 filter => $fai_filter,
2837 );
2838 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2840 if($mesg->{'resultCode'} == 0 &&
2841 $mesg->count != 0) {
2842 foreach my $entry (@{$mesg->{entries}}) {
2843 if($entry->exists('cn')) {
2844 my $tmp_dn= $entry->dn();
2845 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2846 - length($fai_base) - 1 );
2848 # Skip classname and ou dn parts for class
2849 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2851 # Skip classes without releases
2852 if((!defined($tmp_release)) || length($tmp_release)==0) {
2853 next;
2854 }
2856 my $tmp_cn= $entry->get_value('cn');
2857 my $tmp_state= $entry->get_value('FAIstate');
2859 my $tmp_type;
2860 # Get FAI type
2861 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2862 if(grep $_ eq $oclass, @possible_fai_classes) {
2863 $tmp_type= $oclass;
2864 last;
2865 }
2866 }
2868 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2869 # A Subrelease
2870 my @sub_releases = split(/,/, $tmp_release);
2872 # Walk through subreleases and build hash tree
2873 my $hash;
2874 while(my $tmp_sub_release = pop @sub_releases) {
2875 $hash .= "\{'$tmp_sub_release'\}->";
2876 }
2877 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2878 } else {
2879 # A branch, no subrelease
2880 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2881 }
2882 } elsif (!$entry->exists('cn')) {
2883 my $tmp_dn= $entry->dn();
2884 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2885 - length($fai_base) - 1 );
2886 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2888 # Skip classes without releases
2889 if((!defined($tmp_release)) || length($tmp_release)==0) {
2890 next;
2891 }
2893 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2894 # A Subrelease
2895 my @sub_releases= split(/,/, $tmp_release);
2897 # Walk through subreleases and build hash tree
2898 my $hash;
2899 while(my $tmp_sub_release = pop @sub_releases) {
2900 $hash .= "\{'$tmp_sub_release'\}->";
2901 }
2902 # Remove the last two characters
2903 chop($hash);
2904 chop($hash);
2906 eval('$fai_classes->'.$hash.'= {}');
2907 } else {
2908 # A branch, no subrelease
2909 if(!exists($fai_classes->{$tmp_release})) {
2910 $fai_classes->{$tmp_release} = {};
2911 }
2912 }
2913 }
2914 }
2916 # The hash is complete, now we can honor the copy-on-write based missing entries
2917 foreach my $release (keys %$fai_classes) {
2918 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2919 }
2920 }
2921 return $result;
2922 }
2924 sub apply_fai_inheritance {
2925 my $fai_classes = shift || return {};
2926 my $tmp_classes;
2928 # Get the classes from the branch
2929 foreach my $class (keys %{$fai_classes}) {
2930 # Skip subreleases
2931 if($class =~ /^ou=.*$/) {
2932 next;
2933 } else {
2934 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2935 }
2936 }
2938 # Apply to each subrelease
2939 foreach my $subrelease (keys %{$fai_classes}) {
2940 if($subrelease =~ /ou=/) {
2941 foreach my $tmp_class (keys %{$tmp_classes}) {
2942 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2943 $fai_classes->{$subrelease}->{$tmp_class} =
2944 deep_copy($tmp_classes->{$tmp_class});
2945 } else {
2946 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2947 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2948 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2949 deep_copy($tmp_classes->{$tmp_class}->{$type});
2950 }
2951 }
2952 }
2953 }
2954 }
2955 }
2957 # Find subreleases in deeper levels
2958 foreach my $subrelease (keys %{$fai_classes}) {
2959 if($subrelease =~ /ou=/) {
2960 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2961 if($subsubrelease =~ /ou=/) {
2962 apply_fai_inheritance($fai_classes->{$subrelease});
2963 }
2964 }
2965 }
2966 }
2968 return $fai_classes;
2969 }
2971 sub get_fai_release_entries {
2972 my $tmp_classes = shift || return;
2973 my $parent = shift || "";
2974 my @result = shift || ();
2976 foreach my $entry (keys %{$tmp_classes}) {
2977 if(defined($entry)) {
2978 if($entry =~ /^ou=.*$/) {
2979 my $release_name = $entry;
2980 $release_name =~ s/ou=//g;
2981 if(length($parent)>0) {
2982 $release_name = $parent."/".$release_name;
2983 }
2984 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2985 foreach my $bufentry(@bufentries) {
2986 push @result, $bufentry;
2987 }
2988 } else {
2989 my @types = get_fai_types($tmp_classes->{$entry});
2990 foreach my $type (@types) {
2991 push @result,
2992 {
2993 'class' => $entry,
2994 'type' => $type->{'type'},
2995 'release' => $parent,
2996 'state' => $type->{'state'},
2997 };
2998 }
2999 }
3000 }
3001 }
3003 return @result;
3004 }
3006 sub deep_copy {
3007 my $this = shift;
3008 if (not ref $this) {
3009 $this;
3010 } elsif (ref $this eq "ARRAY") {
3011 [map deep_copy($_), @$this];
3012 } elsif (ref $this eq "HASH") {
3013 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
3014 } else { die "what type is $_?" }
3015 }
3018 sub session_run_result {
3019 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
3020 $kernel->sig(CHLD => "child_reap");
3021 }
3023 sub session_run_debug {
3024 my $result = $_[ARG0];
3025 print STDERR "$result\n";
3026 }
3028 sub session_run_done {
3029 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
3030 delete $heap->{task}->{$task_id};
3031 if (exists $heap->{ldap_handle}->{$task_id}) {
3032 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
3033 }
3034 delete $heap->{ldap_handle}->{$task_id};
3035 }
3038 sub create_sources_list {
3039 my $session_id = shift || 0;
3040 my $result="/tmp/gosa_si_tmp_sources_list";
3042 # Remove old file
3043 if(stat($result)) {
3044 unlink($result);
3045 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
3046 }
3048 open(my $fh, ">", "$result");
3049 if (not defined $fh) {
3050 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
3051 return undef;
3052 }
3053 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
3054 my $ldap_handle = &get_ldap_handle($session_id);
3055 my $mesg=$ldap_handle->search(
3056 base => $main::ldap_server_dn,
3057 scope => 'base',
3058 attrs => 'FAIrepository',
3059 filter => 'objectClass=FAIrepositoryServer'
3060 );
3061 if($mesg->count) {
3062 foreach my $entry(@{$mesg->{'entries'}}) {
3063 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
3064 my ($server, $tag, $release, $sections)= split /\|/, $value;
3065 my $line = "deb $server $release";
3066 $sections =~ s/,/ /g;
3067 $line.= " $sections";
3068 print $fh $line."\n";
3069 }
3070 }
3071 }
3072 &release_ldap_handle($ldap_handle);
3073 } else {
3074 if (defined $main::ldap_server_dn){
3075 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
3076 } else {
3077 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
3078 }
3079 }
3080 close($fh);
3082 return $result;
3083 }
3086 sub run_create_packages_list_db {
3087 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
3088 my $session_id = $session->ID;
3089 my $task = POE::Wheel::Run->new(
3090 Priority => +20,
3091 Program => sub {&create_packages_list_db(undef, $session_id)},
3092 StdoutEvent => "session_run_result",
3093 StderrEvent => "session_run_debug",
3094 CloseEvent => "session_run_done",
3095 );
3096 $heap->{task}->{ $task->ID } = $task;
3097 }
3100 sub create_packages_list_db {
3101 my ($sources_file, $session_id) = @_;
3103 # it should not be possible to trigger a recreation of packages_list_db
3104 # while packages_list_db is under construction, so set flag packages_list_under_construction
3105 # which is tested befor recreation can be started
3106 if (-r $packages_list_under_construction) {
3107 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3108 return;
3109 } else {
3110 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
3111 # set packages_list_under_construction to true
3112 system("touch $packages_list_under_construction");
3113 @packages_list_statements=();
3114 }
3116 if (not defined $session_id) { $session_id = 0; }
3118 if (not defined $sources_file) {
3119 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
3120 $sources_file = &create_sources_list($session_id);
3121 }
3123 if (not defined $sources_file) {
3124 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
3125 unlink($packages_list_under_construction);
3126 return;
3127 }
3129 my $line;
3131 open(my $CONFIG, "<", "$sources_file") or do {
3132 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3133 unlink($packages_list_under_construction);
3134 return;
3135 };
3137 # Read lines
3138 while ($line = <$CONFIG>){
3139 # Unify
3140 chop($line);
3141 $line =~ s/^\s+//;
3142 $line =~ s/^\s+/ /;
3144 # Strip comments
3145 $line =~ s/#.*$//g;
3147 # Skip empty lines
3148 if ($line =~ /^\s*$/){
3149 next;
3150 }
3152 # Interpret deb line
3153 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3154 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3155 my $section;
3156 foreach $section (split(' ', $sections)){
3157 &parse_package_info( $baseurl, $dist, $section, $session_id );
3158 }
3159 }
3160 else {
3161 daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3162 }
3163 }
3165 close ($CONFIG);
3167 if(keys(%repo_dirs)) {
3168 find(\&cleanup_and_extract, keys( %repo_dirs ));
3169 &main::strip_packages_list_statements();
3170 $packages_list_db->exec_statementlist(\@packages_list_statements);
3171 }
3172 unlink($packages_list_under_construction);
3173 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
3174 return;
3175 }
3177 # This function should do some intensive task to minimize the db-traffic
3178 sub strip_packages_list_statements {
3179 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3180 my @new_statement_list=();
3181 my $hash;
3182 my $insert_hash;
3183 my $update_hash;
3184 my $delete_hash;
3185 my $known_packages_hash;
3186 my $local_timestamp=get_time();
3188 foreach my $existing_entry (@existing_entries) {
3189 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3190 }
3192 foreach my $statement (@packages_list_statements) {
3193 if($statement =~ /^INSERT/i) {
3194 # Assign the values from the insert statement
3195 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
3196 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3197 if(exists($hash->{$distribution}->{$package}->{$version})) {
3198 # If section or description has changed, update the DB
3199 if(
3200 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
3201 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3202 ) {
3203 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3204 } else {
3205 # package is already present in database. cache this knowledge for later use
3206 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3207 }
3208 } else {
3209 # Insert a non-existing entry to db
3210 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3211 }
3212 } elsif ($statement =~ /^UPDATE/i) {
3213 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3214 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
3215 foreach my $distribution (keys %{$hash}) {
3216 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3217 # update the insertion hash to execute only one query per package (insert instead insert+update)
3218 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3219 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3220 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3221 my $section;
3222 my $description;
3223 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3224 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3225 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3226 }
3227 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3228 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3229 }
3230 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3231 }
3232 }
3233 }
3234 }
3235 }
3237 # Check for orphaned entries
3238 foreach my $existing_entry (@existing_entries) {
3239 my $distribution= @{$existing_entry}[0];
3240 my $package= @{$existing_entry}[1];
3241 my $version= @{$existing_entry}[2];
3242 my $section= @{$existing_entry}[3];
3244 if(
3245 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3246 exists($update_hash->{$distribution}->{$package}->{$version}) ||
3247 exists($known_packages_hash->{$distribution}->{$package}->{$version})
3248 ) {
3249 next;
3250 } else {
3251 # Insert entry to delete hash
3252 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3253 }
3254 }
3256 # unroll the insert hash
3257 foreach my $distribution (keys %{$insert_hash}) {
3258 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3259 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3260 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3261 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3262 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3263 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3264 ."'$local_timestamp')";
3265 }
3266 }
3267 }
3269 # unroll the update hash
3270 foreach my $distribution (keys %{$update_hash}) {
3271 foreach my $package (keys %{$update_hash->{$distribution}}) {
3272 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3273 my $set = "";
3274 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3275 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3276 }
3277 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3278 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3279 }
3280 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3281 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3282 }
3283 if(defined($set) and length($set) > 0) {
3284 $set .= "timestamp = '$local_timestamp'";
3285 } else {
3286 next;
3287 }
3288 push @new_statement_list,
3289 "UPDATE $main::packages_list_tn SET $set WHERE"
3290 ." distribution = '$distribution'"
3291 ." AND package = '$package'"
3292 ." AND version = '$version'";
3293 }
3294 }
3295 }
3297 # unroll the delete hash
3298 foreach my $distribution (keys %{$delete_hash}) {
3299 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3300 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3301 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3302 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3303 }
3304 }
3305 }
3307 unshift(@new_statement_list, "VACUUM");
3309 @packages_list_statements = @new_statement_list;
3310 }
3313 sub parse_package_info {
3314 my ($baseurl, $dist, $section, $session_id)= @_;
3315 my ($package);
3316 if (not defined $session_id) { $session_id = 0; }
3317 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3318 $repo_dirs{ "${repo_path}/pool" } = 1;
3320 foreach $package ("Packages.gz"){
3321 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3322 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3323 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3324 }
3326 }
3329 sub get_package {
3330 my ($url, $dest, $session_id)= @_;
3331 if (not defined $session_id) { $session_id = 0; }
3333 my $tpath = dirname($dest);
3334 -d "$tpath" || mkpath "$tpath";
3336 # This is ugly, but I've no time to take a look at "how it works in perl"
3337 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3338 system("gunzip -cd '$dest' > '$dest.in'");
3339 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3340 unlink($dest);
3341 daemon_log("$session_id DEBUG: delete file '$dest'", 266);
3342 } else {
3343 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3344 }
3345 return 0;
3346 }
3349 sub parse_package {
3350 my ($path, $dist, $srv_path, $session_id)= @_;
3351 if (not defined $session_id) { $session_id = 0;}
3352 my ($package, $version, $section, $description);
3353 my $timestamp = &get_time();
3355 if(not stat("$path.in")) {
3356 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3357 return;
3358 }
3360 open(my $PACKAGES, "<", "$path.in");
3361 if(not defined($PACKAGES)) {
3362 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3363 return;
3364 }
3366 # Read lines
3367 while (<$PACKAGES>){
3368 my $line = $_;
3369 # Unify
3370 chop($line);
3372 # Use empty lines as a trigger
3373 if ($line =~ /^\s*$/){
3374 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3375 push(@packages_list_statements, $sql);
3376 $package = "none";
3377 $version = "none";
3378 $section = "none";
3379 $description = "none";
3380 next;
3381 }
3383 # Trigger for package name
3384 if ($line =~ /^Package:\s/){
3385 ($package)= ($line =~ /^Package: (.*)$/);
3386 next;
3387 }
3389 # Trigger for version
3390 if ($line =~ /^Version:\s/){
3391 ($version)= ($line =~ /^Version: (.*)$/);
3392 next;
3393 }
3395 # Trigger for description
3396 if ($line =~ /^Description:\s/){
3397 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3398 next;
3399 }
3401 # Trigger for section
3402 if ($line =~ /^Section:\s/){
3403 ($section)= ($line =~ /^Section: (.*)$/);
3404 next;
3405 }
3407 # Trigger for filename
3408 if ($line =~ /^Filename:\s/){
3409 my ($filename) = ($line =~ /^Filename: (.*)$/);
3410 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3411 next;
3412 }
3413 }
3415 close( $PACKAGES );
3416 unlink( "$path.in" );
3417 }
3420 sub store_fileinfo {
3421 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3423 my %fileinfo = (
3424 'package' => $package,
3425 'dist' => $dist,
3426 'version' => $vers,
3427 );
3429 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3430 }
3433 sub cleanup_and_extract {
3434 my $fileinfo = $repo_files{ $File::Find::name };
3436 if( defined $fileinfo ) {
3437 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3438 my $sql;
3439 my $package = $fileinfo->{ 'package' };
3440 my $newver = $fileinfo->{ 'version' };
3442 mkpath($dir);
3443 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3445 if( -f "$dir/DEBIAN/templates" ) {
3447 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3449 my $tmpl= ""; {
3450 local $/=undef;
3451 open(my $FILE, "$dir/DEBIAN/templates");
3452 $tmpl = &encode_base64(<$FILE>);
3453 close($FILE);
3454 }
3455 rmtree("$dir/DEBIAN/templates");
3457 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3458 push @packages_list_statements, $sql;
3459 }
3460 }
3462 return;
3463 }
3466 sub prepare_server_registration
3467 {
3468 # Add foreign server from cfg file
3469 my @foreign_server_list;
3470 if ($foreign_server_string ne "") {
3471 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3472 foreach my $foreign_server (@cfg_foreign_server_list) {
3473 push(@foreign_server_list, $foreign_server);
3474 }
3476 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3477 }
3479 # Perform a DNS lookup for server registration if flag is true
3480 if ($dns_lookup eq "true") {
3481 # Add foreign server from dns
3482 my @tmp_servers;
3483 if (not $server_domain) {
3484 # Try our DNS Searchlist
3485 for my $domain(get_dns_domains()) {
3486 chomp($domain);
3487 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3488 if(@$tmp_domains) {
3489 for my $tmp_server(@$tmp_domains) {
3490 push @tmp_servers, $tmp_server;
3491 }
3492 }
3493 }
3494 if(@tmp_servers && length(@tmp_servers)==0) {
3495 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3496 }
3497 } else {
3498 @tmp_servers = &get_server_addresses($server_domain);
3499 if( 0 == @tmp_servers ) {
3500 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3501 }
3502 }
3504 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3506 foreach my $server (@tmp_servers) {
3507 unshift(@foreign_server_list, $server);
3508 }
3509 } else {
3510 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3511 }
3513 # eliminate duplicate entries
3514 @foreign_server_list = &del_doubles(@foreign_server_list);
3515 my $all_foreign_server = join(", ", @foreign_server_list);
3516 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3518 # add all found foreign servers to known_server
3519 my $cur_timestamp = &get_time();
3520 foreach my $foreign_server (@foreign_server_list) {
3522 # do not add myself to known_server_db
3523 if (&is_local($foreign_server)) { next; }
3524 ######################################
3526 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3527 primkey=>['hostname'],
3528 hostname=>$foreign_server,
3529 macaddress=>"",
3530 status=>'not_yet_registered',
3531 hostkey=>"none",
3532 loaded_modules => "none",
3533 timestamp=>$cur_timestamp,
3534 update_time=>'19700101000000',
3535 } );
3536 }
3537 }
3539 sub register_at_foreign_servers {
3540 my ($kernel) = $_[KERNEL];
3542 # Update status and update-time of all si-server with expired update_time and
3543 # block them for race conditional registration processes of other si-servers.
3544 my $act_time = &get_time();
3545 my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3546 my $block_res = $known_server_db->exec_statement($block_statement);
3548 # Fetch all si-server from db where update_time is younger than act_time
3549 my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'";
3550 my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3552 # Detect already connected clients. Will be added to registration msg later.
3553 my $client_sql = "SELECT * FROM $known_clients_tn";
3554 my $client_res = $known_clients_db->exec_statement($client_sql);
3556 # Send registration messag to all fetched si-server
3557 foreach my $hit (@$fetch_res) {
3558 my $hostname = @$hit[0];
3559 my $hostkey = &create_passwd;
3561 # Add already connected clients to registration message
3562 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3563 &add_content2xml_hash($myhash, 'key', $hostkey);
3564 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3566 # Add locally loaded gosa-si modules to registration message
3567 my $loaded_modules = {};
3568 while (my ($package, $pck_info) = each %$known_modules) {
3569 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3570 foreach my $act_module (keys(%{@$pck_info[2]})) {
3571 $loaded_modules->{$act_module} = "";
3572 }
3573 }
3574 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3576 # Add macaddress to registration message
3577 my ($host_ip, $host_port) = split(/:/, $hostname);
3578 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3579 my $network_interface= &get_interface_for_ip($local_ip);
3580 my $host_mac = &get_mac_for_interface($network_interface);
3581 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3583 # Build registration message and send it
3584 my $foreign_server_msg = &create_xml_string($myhash);
3585 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3586 }
3589 # After n sec perform a check of all server registration processes
3590 $kernel->delay_set("control_server_registration", 2);
3592 return;
3593 }
3596 sub control_server_registration {
3597 my ($kernel) = $_[KERNEL];
3599 # Check if all registration processes succeed or not
3600 my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'";
3601 my $select_res = $known_server_db->exec_statement($select_statement);
3603 # If at least one registration process failed, maybe in case of a race condition
3604 # with a foreign registration process
3605 if (@$select_res > 0)
3606 {
3607 # Release block statement 'new_server' to make the server accessible
3608 # for foreign registration processes
3609 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";
3610 my $update_res = $known_server_db->exec_statement($update_statement);
3612 # Set a random delay to avoid the registration race condition
3613 my $new_foreign_servers_register_delay = int(rand(4))+1;
3614 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3615 }
3616 # If all registration processes succeed
3617 else
3618 {
3619 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3620 }
3622 return;
3623 }
3625 sub start_daemon {
3627 if( ! $foreground ) {
3628 chdir '/' or die "Can't chdir to /: $!";
3629 umask 0;
3630 open STDIN, '+>/dev/null' or die "Can't read /dev/null: $!";
3631 open STDOUT, '+>&STDIN' or die "Can't write to /dev/null: $!";
3632 open STDERR, '+>&STDIN' or die "Can't write to /dev/null: $!";
3633 defined(my $child_pid = fork) or die "Can't fork: $!";
3634 exit if $child_pid;
3635 setsid or die "Can't start a new session: $!";
3636 }
3637 return;
3638 }
3640 sub put_version {
3642 # parse head url and revision from svn
3643 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3644 $server_headURL = defined $1 ? $1 : 'unknown' ;
3645 $server_revision = defined $2 ? $2 : 'unknown' ;
3646 if ($server_headURL =~ /\/tag\// ||
3647 $server_headURL =~ /\/branches\// ) {
3648 $server_status = "stable";
3649 } else {
3650 $server_status = "developmental" ;
3651 }
3652 return;
3653 }
3655 sub get_perms_admin {
3656 # Determine root uid and adm gid, used for creating log files
3657 $root_uid = getpwnam('root');
3658 $adm_gid = getgrnam('adm');
3659 if(not defined $adm_gid){
3660 $adm_gid = getgrnam('root');
3661 }
3662 return;
3663 }
3665 sub open_log {
3666 # Prepare log file and set permissions
3667 open(my $log, ">>", "$log_file");
3668 close($log);
3669 chmod(0440, $log_file);
3670 chown($root_uid, $adm_gid, $log_file);
3672 return;
3673 }
3675 sub create_pid {
3676 # Create the PID object
3677 $pid = File::Pid->new({ file => $pid_file });
3679 # Write the PID file
3680 $pid->write;
3682 return;
3683 }
3685 #
3686 #==== MAIN = main ==============================================================
3687 #
3688 # Parse options and allow '-vvv'
3689 Getopt::Long::Configure( 'bundling' );
3690 GetOptions( 'v|verbose+' => \$verbose,
3691 'h|help' => \&usage,
3692 'c|config=s' => \$config,
3693 'x|dump-config=i' => \$dump_config,
3694 'f|foreground' => \$foreground,
3695 'd=s' => \$debug_parts)
3696 or usage( '', 1 );
3698 # We may want to dump the default configuration
3699 if( defined $dump_config ) {
3700 if($dump_config==1) {
3701 } elsif ($dump_config==2) {
3702 dump_configuration( $dump_config );
3703 } else {
3704 usage( "Dump configuration value has to be 1 or 2" );
3705 }
3706 }
3708 # read and set config parameters
3709 &read_configfile($config, %cfg_defaults);
3711 # create pid file
3712 &create_pid($pid, $pid_file);
3714 # daemonize the program
3715 &start_daemon($foreground);
3717 # Determine root uid and adm gid, used for creating log files
3718 &get_perms_admin($root_uid, $adm_gid);
3720 # put version
3721 &put_version($server_status_hash, $server_version, $server_headURL, $server_revision, $server_status);
3723 #open log file
3724 &open_log($root_uid, $adm_gid, $log_file);
3726 # prepare directory for databases
3727 mkpath('/var/lib/gosa-si', 0, {owner=>'root', group=>'root'});
3729 # remove leftover files in tmp for packaged.db populate
3730 rmtree( '/tmp/packages_list_db',0,1);
3732 # remove list of sources from apt.sources.list
3733 unlink '/tmp/gosa_si_tmp_sources_list';
3735 # remove marker that the list creation is in progress
3736 unlink '/tmp/packages_list_creation_in_progress';
3738 daemon_log(" ", 1);
3739 daemon_log("$0 started!", 1);
3740 daemon_log("status: $server_status", 1);
3741 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3743 # Buildup data bases
3744 {
3745 no strict "refs";
3747 daemon_log("0 INFO: importing database module '$db_module'", 1);
3749 if ($db_module eq "DBmysql") {
3751 # connect to incoming_db
3752 $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3754 # connect to gosa-si job queue
3755 $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3757 # connect to known_clients_db
3758 $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3760 # connect to foreign_clients_db
3761 $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3763 # connect to known_server_db
3764 $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3766 # connect to login_usr_db
3767 $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3769 # connect to fai_server_db
3770 $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3772 # connect to fai_release_db
3773 $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3775 # connect to packages_list_db
3776 $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3778 # connect to messaging_db
3779 $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3781 } elsif ($db_module eq "DBsqlite") {
3783 daemon_log("0 INFO: Removing SQLlite lock files", 1);
3785 # delete old DBsqlite lock files to be replace by rmtree !!
3786 system('rm -f /var/lib/gosa-si/*.si.lock*');
3787 #rmtree( '/var/lib/gosa-si/',0,1);
3789 # connect to incoming_db
3790 unlink($incoming_file_name);
3791 $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3792 #chmod(0640, $incoming_file_name);
3793 #chown($root_uid, $adm_gid, $incoming_file_name);
3795 # connect to gosa-si job queue
3796 $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3797 #chmod(0640, $job_queue_file_name);
3798 #chown($root_uid, $adm_gid, $job_queue_file_name);
3800 # connect to known_clients_db
3801 #unlink($known_clients_file_name);
3802 $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3803 #chmod(0640, $known_clients_file_name);
3804 #chown($root_uid, $adm_gid, $known_clients_file_name);
3806 # connect to foreign_clients_db
3807 #unlink($foreign_clients_file_name);
3808 $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3809 #chmod(0640, $foreign_clients_file_name);
3810 #chown($root_uid, $adm_gid, $foreign_clients_file_name);
3812 # connect to known_server_db
3813 #unlink($known_server_file_name); # do not delete, gosa-si-server should be forced to check config file and dns at each start
3814 $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3815 #chmod(0640, $known_server_file_name);
3816 #chown($root_uid, $adm_gid, $known_server_file_name);
3818 # connect to login_usr_db
3819 #unlink($login_users_file_name);
3820 $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3821 #chmod(0640, $login_users_file_name);
3822 #chown($root_uid, $adm_gid, $login_users_file_name);
3824 # connect to fai_server_db
3825 #unlink($fai_server_file_name);
3826 $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3827 #chmod(0640, $fai_server_file_name);
3828 #chown($root_uid, $adm_gid, $fai_server_file_name);
3830 # connect to fai_release_db
3831 #unlink($fai_release_file_name);
3832 $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3833 #chmod(0640, $fai_release_file_name);
3834 #chown($root_uid, $adm_gid, $fai_release_file_name);
3836 # connect to packages_list_db
3837 #unlink($packages_list_under_construction);
3838 $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3839 #chmod(0640, $packages_list_file_name);
3840 #chown($root_uid, $adm_gid, $packages_list_file_name);
3842 # connect to messaging_db
3843 #unlink($messaging_file_name);
3844 $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3845 #chmod(0640, $messaging_file_name);
3846 #chown($root_uid, $adm_gid, $messaging_file_name);
3847 }
3848 }
3850 # Creating tables
3852 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3854 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3855 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3856 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3857 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3858 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3859 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3860 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3861 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3862 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3863 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3865 # create xml object used for en/decrypting
3866 $xml = new XML::Simple();
3868 # Import all modules
3869 &import_modules;
3871 # Check wether all modules are gosa-si valid passwd check
3872 &password_check;
3874 # Check DNS and config file for server registration
3875 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3877 # Create functions hash
3878 while (my ($module, @mod_info) = each %$known_modules)
3879 {
3880 while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3881 {
3882 while (my ($function, $nothing) = each %$functions )
3883 {
3884 $known_functions->{$function} = $nothing;
3885 }
3886 }
3887 }
3889 # Prepare for using Opsi
3890 if ($opsi_enabled eq "true") {
3891 use JSON::RPC::Client;
3892 use XML::Quote qw(:all);
3893 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3894 $opsi_client = new JSON::RPC::Client;
3895 }
3897 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3899 POE::Component::Server::TCP->new(
3900 Alias => "TCP_SERVER",
3901 Port => $server_port,
3902 ClientInput => sub {
3903 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3904 my $session_id = $session->ID;
3905 if ($input =~ /;([\d\.]+):([\d]+)$/)
3906 {
3907 # Messages from other servers should be blocked if config option is set
3908 if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3909 {
3910 return;
3911 }
3912 &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3913 }
3914 else
3915 {
3916 my $remote_ip = $heap->{'remote_ip'};
3917 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3918 }
3919 push(@msgs_to_decrypt, $input);
3920 $kernel->yield("msg_to_decrypt");
3921 },
3922 InlineStates => {
3923 msg_to_decrypt => \&msg_to_decrypt,
3924 watch_for_next_tasks => \&watch_for_next_tasks,
3925 next_task => \&next_task,
3926 task_result => \&handle_task_result,
3927 task_done => \&handle_task_done,
3928 task_debug => \&handle_task_debug,
3929 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3930 }
3931 );
3933 # create session for repeatedly checking the job queue for jobs
3934 POE::Session->create(
3935 inline_states => {
3936 _start => \&session_start,
3937 register_at_foreign_servers => \®ister_at_foreign_servers,
3938 control_server_registration => \&control_server_registration,
3939 sig_handler => \&sig_handler,
3940 next_task => \&next_task,
3941 task_result => \&handle_task_result,
3942 task_done => \&handle_task_done,
3943 task_debug => \&handle_task_debug,
3944 watch_for_new_messages => \&watch_for_new_messages,
3945 watch_for_delivery_messages => \&watch_for_delivery_messages,
3946 watch_for_done_messages => \&watch_for_done_messages,
3947 watch_for_new_jobs => \&watch_for_new_jobs,
3948 watch_for_modified_jobs => \&watch_for_modified_jobs,
3949 watch_for_done_jobs => \&watch_for_done_jobs,
3950 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3951 watch_for_old_known_clients => \&watch_for_old_known_clients,
3952 create_packages_list_db => \&run_create_packages_list_db,
3953 create_fai_server_db => \&run_create_fai_server_db,
3954 create_fai_release_db => \&run_create_fai_release_db,
3955 recreate_packages_db => \&run_recreate_packages_db,
3956 session_run_result => \&session_run_result,
3957 session_run_debug => \&session_run_debug,
3958 session_run_done => \&session_run_done,
3959 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3960 }
3961 );
3964 POE::Kernel->run();
3965 exit;