#!/usr/bin/perl #=============================================================================== # # FILE: gosa-server # # USAGE: gosa-si-client # # DESCRIPTION: # # OPTIONS: --- # REQUIREMENTS: libnetaddr-ip-perl # BUGS: --- # NOTES: # AUTHOR: (Andreas Rettenberger), # COMPANY: # VERSION: 1.0 # CREATED: 12.09.2007 08:54:41 CEST # REVISION: --- #=============================================================================== use strict; use warnings; use Getopt::Long; use Config::IniFiles; use POSIX; use Time::HiRes qw( gettimeofday ); use File::Basename; use Fcntl; use IO::Socket::INET; use Crypt::Rijndael; use MIME::Base64; use Digest::MD5 qw(md5 md5_hex md5_base64); use XML::Simple; use Data::Dumper; use Sys::Syslog qw( :DEFAULT setlogsock); use File::Spec; use Cwd; use NetAddr::IP; use GOSA::GosaSupportDaemon; my ($cfg_file, %cfg_defaults, $foreground, $verbose, $pid_file, $procid, $pid, $log_file); my ($server_address, $server_ip, $server_port, $server_domain, $server_passwd, $server_cipher, $server_timeout); my ($client_address, $client_ip, $client_port, $client_mac_address, $network_interface, $ldap_config, $pam_config, $nss_config, $gotoHardwareChecksum); my ($input_socket, $rbits, $wbits, $ebits, $xml, $known_hosts, $ldap_enabled); my (@events); # default variables my $event_dir = "/usr/lib/gosa-si/client/events"; $known_hosts = {}; $foreground = 0 ; %cfg_defaults = ("general" => {"log_file" => [\$log_file, "/var/run/".$0.".log"], "pid_file" => [\$pid_file, "/var/run/".$0.".pid"], }, "client" => {"client_port" => [\$client_port, "20083"], "client_ip" => [\$client_ip, "0.0.0.0"], "ldap" => [\$ldap_enabled, 1], "ldap_config" => [\$ldap_config, "/etc/ldap/ldap.conf"], "pam_config" => [\$pam_config, "/etc/pam_ldap.conf"], "nss_config" => [\$nss_config, "/etc/libnss_ldap.conf"], }, "server" => {"server_ip" => [\$server_ip, ""], "server_port" => [\$server_port, "20081"], "server_passwd" => [\$server_passwd, ""], "server_timeout" => [\$server_timeout, 10], "server_domain" => [\$server_domain, ""], }, ); #=== FUNCTION ================================================================ # NAME: read_configfile # PARAMETERS: cfg_file - string - # RETURNS: # DESCRIPTION: #=============================================================================== sub read_configfile { my $cfg; if( defined( $cfg_file) && ( length($cfg_file) > 0 )) { if( -r $cfg_file ) { $cfg = Config::IniFiles->new( -file => $cfg_file ); } else { print STDERR "Couldn't read config file!"; } } else { $cfg = Config::IniFiles->new() ; } foreach my $section (keys %cfg_defaults) { foreach my $param (keys %{$cfg_defaults{ $section }}) { my $pinfo = $cfg_defaults{ $section }{ $param }; ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] ); } } } #=== FUNCTION ================================================================ # NAME: logging # PARAMETERS: level - string - default 'info' # msg - string - # facility - string - default 'LOG_DAEMON' # RETURNS: # DESCRIPTION: #=============================================================================== sub daemon_log { my( $msg, $level ) = @_; if(not defined $msg) { return } if(not defined $level) { $level = 1 } if(defined $log_file){ open(LOG_HANDLE, ">>$log_file"); if(not defined open( LOG_HANDLE, ">>$log_file" )) { print STDERR "cannot open $log_file: $!"; return } chomp($msg); if($level <= $verbose){ print LOG_HANDLE $msg."\n"; if(defined $foreground) { print $msg."\n" } } } close( LOG_HANDLE ); # my ($msg, $level, $facility) = @_; # if(not defined $msg) {return} # if(not defined $level) {$level = "info"} # if(not defined $facility) {$facility = "LOG_DAEMON"} # openlog($0, "pid,cons,", $facility); # syslog($level, $msg); # closelog; # return; } #=== FUNCTION ================================================================ # NAME: check_cmdline_param # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub check_cmdline_param () { my $err_config; my $err_counter = 0; if(not defined($cfg_file)) { $cfg_file = "/etc/gosa-si/client.conf"; if(! -r $cfg_file) { $err_config = "please specify a config file"; $err_counter += 1; } } if( $err_counter > 0 ) { &usage( "", 1 ); if( defined( $err_config)) { print STDERR "$err_config\n"} print STDERR "\n"; exit( -1 ); } } #=== FUNCTION ================================================================ # NAME: check_pid # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub check_pid { $pid = -1; # Check, if we are already running if( open(LOCK_FILE, "<$pid_file") ) { $pid = ; if( defined $pid ) { chomp( $pid ); if( -f "/proc/$pid/stat" ) { my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/; if( $0 eq $stat ) { close( LOCK_FILE ); exit -1; } } } close( LOCK_FILE ); unlink( $pid_file ); } # create a syslog msg if it is not to possible to open PID file if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) { my($msg) = "Couldn't obtain lockfile '$pid_file' "; if (open(LOCK_FILE, '<', $pid_file) && ($pid = )) { chomp($pid); $msg .= "(PID $pid)\n"; } else { $msg .= "(unable to read PID)\n"; } if( ! ($foreground) ) { openlog( $0, "cons,pid", "daemon" ); syslog( "warning", $msg ); closelog(); } else { print( STDERR " $msg " ); } exit( -1 ); } } #=== FUNCTION ================================================================ # NAME: get_interface_for_ip # PARAMETERS: ip address (i.e. 192.168.0.1) # RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. #=============================================================================== sub get_interface_for_ip { my $result; my $ip= shift; if ($ip && length($ip) > 0) { my @ifs= &get_interfaces(); if($ip eq "0.0.0.0") { $result = "all"; } else { foreach (@ifs) { my $if=$_; if(get_ip($if) eq $ip) { $result = $if; last; } } } } return $result; } #=== FUNCTION ================================================================ # NAME: get_interfaces # PARAMETERS: none # RETURNS: (list of interfaces) # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. #=============================================================================== sub get_interfaces { my @result; my $PROC_NET_DEV= ('/proc/net/dev'); open(PROC_NET_DEV, "<$PROC_NET_DEV") or die "Could not open $PROC_NET_DEV"; my @ifs = ; close(PROC_NET_DEV); # Eat first two line shift @ifs; shift @ifs; chomp @ifs; foreach my $line(@ifs) { my $if= (split /:/, $line)[0]; $if =~ s/^\s+//; push @result, $if; } return @result; } #=== FUNCTION ================================================================ # NAME: get_mac # PARAMETERS: interface name (i.e. eth0) # RETURNS: (mac address) # DESCRIPTION: Uses ioctl to get mac address directly from system. #=============================================================================== sub get_mac { my $ifreq= shift; my $result; if ($ifreq && length($ifreq) > 0) { if($ifreq eq "all") { if(defined($server_ip)) { $result = &get_local_mac_for_remote_ip($server_ip); } else { $result = "00:00:00:00:00:00"; } } else { my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list # A configured MAC Address should always override a guessed value if ($client_mac_address and length($client_mac_address) > 0) { $result= $client_mac_address; } socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip') or die "socket: $!"; if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) { my ($if, $mac)= unpack 'h36 H12', $ifreq; if (length($mac) > 0) { $mac=~ m/^([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/; $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6); $result = $mac; } } } } return $result; } #=== FUNCTION ================================================================ # NAME: get_ip # PARAMETERS: interface name (i.e. eth0) # RETURNS: (ip address) # DESCRIPTION: Uses ioctl to get ip address directly from system. #=============================================================================== sub get_ip { my $ifreq= shift; my $result= ""; my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list my $proto= getprotobyname('ip'); socket SOCKET, PF_INET, SOCK_DGRAM, $proto or die "socket: $!"; if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) { my ($if, $sin) = unpack 'a16 a16', $ifreq; my ($port, $addr) = sockaddr_in $sin; my $ip = inet_ntoa $addr; if ($ip && length($ip) > 0) { $result = $ip; } } return $result; } #=== FUNCTION ================================================================ # NAME: get_local_mac_for_remote_ip # PARAMETERS: none (takes server_ip from global variable) # RETURNS: (ip address from interface that is used for communication) # DESCRIPTION: Uses ioctl to get routing table from system, checks which entry # matches (defaultroute last). #=============================================================================== sub get_local_mac_for_remote_ip { my $ifreq= shift; my $result= "00:00:00:00:00:00"; my $PROC_NET_ROUTE= ('/proc/net/route'); open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE") or die "Could not open $PROC_NET_ROUTE"; my @ifs = ; close(PROC_NET_ROUTE); # Eat header line shift @ifs; chomp @ifs; foreach my $line(@ifs) { my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line); my $destination; my $mask; my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination); $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d)); ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask); $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d)); if(new NetAddr::IP($server_ip)->within(new NetAddr::IP($destination, $mask))) { # destination matches route, save mac and exit $result= &get_mac($Iface); last; } } return $result; } #=== FUNCTION ================================================================ # NAME: usage # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub usage { my( $text, $help ) = @_; $text = undef if( "h" eq $text ); (defined $text) && print STDERR "\n$text\n"; if( (defined $help && $help) || (!defined $help && !defined $text) ) { print STDERR << "EOF" ; usage: $0 [-hvf] [-c config] -h : this (help) message -c : config file -f : foreground, process will not be forked to background -v : be verbose (multiple to increase verbosity) EOF } print "\n" ; } #=== FUNCTION ================================================================ # NAME: get_server_addresses # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub get_server_addresses { my $domain= shift; my @result; my $dig_cmd= 'dig +nocomments srv _gosad._tcp.'.$domain; my $output= `$dig_cmd 2>&1`; open (PIPE, "$dig_cmd 2>&1 |"); while() { chomp $_; # If it's not a comment if($_ =~ m/^[^;]/) { my @matches= split /\s+/; # Push hostname with port if($matches[3] eq 'SRV') { push @result, $matches[7].':'.$matches[6]; } elsif ($matches[3] eq 'A') { my $i=0; # Substitute the hostname with the ip address of the matching A record foreach my $host (@result) { if ((split /\:/, $host)[0] eq $matches[0]) { $result[$i]= $matches[4].':'.(split /\:/, $host)[1]; } $i++; } } } } close(PIPE); return @result; } #=== FUNCTION ================================================================ # NAME: register_at_server # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub register_at_server { my ($tmp) = @_; # create new passwd and ciphering object for client-server communication my $new_server_passwd = &create_passwd(); my $new_server_cipher; # detect all client accepted events opendir(DIR, $event_dir) or daemon_log("cannot find directory $event_dir!\ngosa-si-client starts without any accepting events!", 1); my $file_name; my @events_list = (); while(defined($file_name = readdir(DIR))){ if ($file_name eq "." || $file_name eq "..") { next; } push(@events_list, $file_name); } my $events = join(",", @events_list); daemon_log("found events: $events", 1); # fill in all possible servers my @servers; if (defined $server_domain) { my @tmp_servers = &get_server_addresses($server_domain); foreach my $server (@tmp_servers) { unshift(@servers, $server); } } # add server address from config file at first position of server list if (defined $server_address) { unshift(@servers, $server_address); } daemon_log("found servers in configuration file and via DNS:", 5); foreach my $server (@servers) { daemon_log("\t$server", 5); } my ($rout, $wout, $reg_server); foreach my $server (@servers) { # create msg hash my $register_hash = &create_xml_hash("here_i_am", $client_address, $server); &add_content2xml_hash($register_hash, "new_passwd", $new_server_passwd); &add_content2xml_hash($register_hash, "mac_address", $client_mac_address); &add_content2xml_hash($register_hash, "events", $events); &add_content2xml_hash($register_hash, "gotoHardwareChecksum", $gotoHardwareChecksum); # send xml hash to server with general server passwd my $answer = &send_msg_hash2address($register_hash, $server, $server_passwd); if ($answer != 0) { next; } # waiting for response daemon_log("waiting for response...\n", 5); my $nf = select($rout=$rbits, $wout=$wbits, undef, $server_timeout); # something is coming in if(vec $rout, fileno $input_socket, 1) { my $crypted_msg; my $client = $input_socket->accept(); my $other_end = getpeername($client); if(not defined $other_end) { daemon_log("client cannot be identified: $!\n"); } else { my ($port, $iaddr) = unpack_sockaddr_in($other_end); my $actual_ip = inet_ntoa($iaddr); daemon_log("\naccept client from $actual_ip\n", 5); my $in_msg = &read_from_socket($client); if(defined $in_msg){ chomp($in_msg); $crypted_msg = $in_msg; } else { daemon_log("cannot read from $actual_ip\n", 5); } } close($client); # validate acknowledge msg from server $new_server_cipher = &create_ciphering($new_server_passwd); my $msg_hash; eval { my $decrypted_msg = &decrypt_msg($crypted_msg, $new_server_cipher); daemon_log("decrypted register msg: $decrypted_msg", 5); $msg_hash = $xml->XMLin($decrypted_msg, ForceArray=>1); }; if($@) { daemon_log("ERROR: do not understand the incoming message:" , 5); daemon_log("$@", 7); } else { my $header = @{$msg_hash->{header}}[0]; if($header eq "registered") { $reg_server = $server; last; } elsif($header eq "denied") { my $reason = (&get_content_from_xml_hash($msg_hash, "denied"))[0]; daemon_log("registration at $server denied: $reason", 1); } else { daemon_log("cannot register at $server", 1); } } } # if no answer arrive, try next server in list } if(defined $reg_server) { daemon_log("registered at $reg_server", 1); } else { daemon_log("cannot register at any server", 1); daemon_log("exiting!!!", 1); exit(1); } # update the global available variables $server_address = $reg_server; $server_passwd = $new_server_passwd; $server_cipher = $new_server_cipher; return; } #=== FUNCTION ================================================================ # NAME: create_xml_hash # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== #sub create_xml_hash { # my ($header, $source, $target, $header_value) = @_; # my $hash = { # header => [$header], # source => [$source], # target => [$target], # $header => [$header_value], # }; # daemon_log("create_xml_hash:", 7), # chomp(my $tmp = Dumper $hash); # daemon_log("\t$tmp\n", 7); # return $hash #} #=== FUNCTION ================================================================ # NAME: create_xml_string # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== #sub create_xml_string { # my ($xml_hash) = @_ ; # my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml'); # $xml_string =~ s/[\n]+//g; # daemon_log("create_xml_string:\n\t$xml_string\n", 7); # return $xml_string; #} #=== FUNCTION ================================================================ # NAME: add_content2xml_hash # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== #sub add_content2xml_hash { # my ($xml_ref, $element, $content) = @_; # if(not exists $$xml_ref{$element} ) { # $$xml_ref{$element} = []; # } # my $tmp = $$xml_ref{$element}; # push(@$tmp, $content); # return; #} #=== FUNCTION ================================================================ # NAME: get_content_from_xml_hash # PARAMETERS: ref : reference to the xml hash # string: key of the value you want # RETURNS: STRING AND ARRAY # DESCRIPTION: if key of the hash is either 'header', 'target' or 'source' the # function returns a string cause it is expected that these keys # do just have one value, all other keys returns an array!!! #=============================================================================== #sub get_content_from_xml_hash { # my ($xml_ref, $element) = @_; # my $result = $xml_ref->{$element}; # if( $element eq "header" || $element eq "target" || $element eq "source") { # return @$result[0]; # } # return @$result; #} # my ($xml_ref, $element) = @_; # if (exists $xml_ref->{$element}) { # my $result = $xml_ref->{$element}; # if( $element eq "header" || $element eq "target" || $element eq "source") { # return @$result[0]; # } else { # return @$result; # } # # } else { # my $result = (); # return @$result; # } #} #=== FUNCTION ================================================================ # NAME: encrypt_msg # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== #sub encrypt_msg { # my ($msg, $my_cipher) = @_; # if(not defined $my_cipher) { print "no cipher object\n"; } # $msg = "\0"x(16-length($msg)%16).$msg; # my $crypted_msg = $my_cipher->encrypt($msg); # chomp($crypted_msg = &encode_base64($crypted_msg)); # return $crypted_msg; #} #=== FUNCTION ================================================================ # NAME: decrypt_msg # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== #sub decrypt_msg { # my ($crypted_msg, $my_cipher) = @_ ; # $crypted_msg = &decode_base64($crypted_msg); # my $msg = $my_cipher->decrypt($crypted_msg); # $msg =~ s/\0*//g; # return $msg; #} #=== FUNCTION ================================================================ # NAME: create_ciphering # PARAMETERS: # RETURNS: cipher object # DESCRIPTION: #=============================================================================== #sub create_ciphering { # my ($passwd) = @_; # $passwd = substr(md5_hex("$passwd") x 32, 0, 32); # my $iv = substr(md5_hex('GONICUS GmbH'),0, 16); # # #daemon_log("iv: $iv", 7); # #daemon_log("key: $passwd", 7); # my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC()); # $my_cipher->set_iv($iv); # return $my_cipher; #} #=== FUNCTION ================================================================ # NAME: create_passwd # PARAMETERS: # RETURNS: cipher object # DESCRIPTION: #=============================================================================== sub create_passwd { my $new_passwd = ""; for(my $i=0; $i<31; $i++) { $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))] } return $new_passwd; } #=== FUNCTION ================================================================ # NAME: send_msg_hash2address # PARAMETERS: msg string - xml message # PeerAddr string - socket address to send msg # PeerPort string - socket port, if not included in socket address # RETURNS: nothing # DESCRIPTION: ???? #=============================================================================== #sub send_msg_hash2address { # my ($msg_hash, $address, $passwd) = @_ ; # # # fetch header for logging # my $header = @{$msg_hash->{header}}[0]; # # # generiere xml string # my $msg_xml = &create_xml_string($msg_hash); # # # hole das entsprechende passwd aus dem hash # if(not defined $passwd) { # if(exists $known_hosts->{$address}) { # $passwd = $known_hosts->{$address}->{passwd}; # } elsif ($address eq $server_address) { # $passwd = $server_passwd; # } else { # daemon_log("$address not known, neither as server nor as client", 1); # return "failed"; # } # } # # # erzeuge ein ciphering object # my $act_cipher = &create_ciphering($passwd); # # # encrypt xml msg # my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher); # # # öffne socket # my $socket = &open_socket($address); # if(not defined $socket){ # daemon_log("cannot open socket to $address, server not reachable", 1); # daemon_log("cannot send '$header'-msg", 1); # return "failed"; # } # # # versende xml msg # print $socket $crypted_msg."\n"; # # # schließe socket # close $socket; # # daemon_log("send '$header'-msg to $address", 5); # daemon_log("crypted_msg:\n\t$crypted_msg", 7); # # return "done"; #} #=== FUNCTION ================================================================ # NAME: open_socket # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000 # [PeerPort] string necessary if port not appended by PeerAddr # RETURNS: socket IO::Socket::INET # DESCRIPTION: #=============================================================================== sub open_socket { my ($PeerAddr, $PeerPort) = @_ ; if(defined($PeerPort)){ $PeerAddr = $PeerAddr.":".$PeerPort; } my $socket; $socket = new IO::Socket::INET(PeerAddr => $PeerAddr , Porto => "tcp" , Type => SOCK_STREAM, Timeout => 5, ); if(not defined $socket) { #daemon_log("cannot connect to socket at $PeerAddr, $@\n"); return; } daemon_log("open_socket:\n\t$PeerAddr", 7); return $socket; } #=== FUNCTION ================================================================ # NAME: read_from_socket # PARAMETERS: socket fh - # RETURNS: result string - readed characters from socket # DESCRIPTION: reads data from socket in 16 byte steps #=============================================================================== sub read_from_socket { my ($socket) = @_; my $result = ""; $socket->blocking(1); $result = <$socket>; $socket->blocking(0); while ( my $char = <$socket> ) { if (not defined $char) { last } $result .= $char; } return $result; # my ($socket) = @_; # my $result = ""; # my $len = 16; # while($len == 16){ # my $char; # $len = sysread($socket, $char, 16); # if($len != 16) { last } # if($len != 16) { last } # $result .= $char; # } # return $result; } #=== FUNCTION ================================================================ # NAME: print_known_hosts_hash # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub print_known_hosts_hash { my ($tmp) = @_; print "####################################\n"; print "# status of known_hosts\n"; my $hosts; my $host_hash; my @hosts = keys %$known_hosts; foreach my $host (@hosts) { #my @elements = keys %$known_hosts->{$host}; my $status = $known_hosts->{$host}->{status} ; my $passwd = $known_hosts->{$host}->{passwd}; my $timestamp = $known_hosts->{$host}->{timestamp}; print "$host\n"; print "\t$status\n"; print "\t$passwd\n"; print "\t$timestamp\n"; } print "####################################\n"; return; } #=== FUNCTION ================================================================ # NAME: # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub create_known_hosts_entry { my ($hostname) = @_; $known_hosts->{$hostname} = {}; $known_hosts->{$hostname}->{status} = "none"; $known_hosts->{$hostname}->{passwd} = "none"; $known_hosts->{$hostname}->{timestamp} = "none"; return; } #=== FUNCTION ================================================================ # NAME: # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub update_known_hosts_entry { my ($hostname, $status, $passwd, $timestamp) = @_; my ($seconds, $minutes, $hours, $monthday, $month, $year, $weekday, $yearday, $sommertime) = localtime(time); $hours = $hours < 10 ? $hours = "0".$hours : $hours; $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; $month+=1; $month = $month < 10 ? $month = "0".$month : $month; $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; $year+=1900; my $t = "$year$month$monthday$hours$minutes$seconds"; if($status) { $known_hosts->{$hostname}->{status} = $status; } if($passwd) { $known_hosts->{$hostname}->{passwd} = $passwd; } if($timestamp) { $t = $timestamp; } $known_hosts->{$hostname}->{timestamp} = $t; return; } #=== FUNCTION ================================================================ # NAME: # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub add_content2known_hosts { my ($hostname, $element, $content) = @_; my ($seconds, $minutes, $hours, $monthday, $month, $year, $weekday, $yearday, $sommertime) = localtime(time); $hours = $hours < 10 ? $hours = "0".$hours : $hours; $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; $month+=1; $month = $month < 10 ? $month = "0".$month : $month; $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; $year+=1900; my $t = "$year$month$monthday$hours$minutes$seconds"; $known_hosts->{$hostname}->{$element} = $content; $known_hosts->{$hostname}->{timestamp} = $t; return; } #=== FUNCTION ================================================================ # NAME: # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub process_incoming_msg { my ($crypted_msg) = @_; if(not defined $crypted_msg) { daemon_log("function 'process_incoming_msg': got no msg", 7); } $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/; $crypted_msg = $1; my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5); daemon_log("msg from host:", 1); daemon_log("\t$host", 1); daemon_log("crypted msg:", 7); daemon_log("\t$crypted_msg", 7); my $act_cipher = &create_ciphering($server_passwd); # try to decrypt incoming msg my ($msg, $msg_hash); eval{ $msg = &decrypt_msg($crypted_msg, $act_cipher); $msg_hash = $xml->XMLin($msg, ForceArray=>1); }; if($@) { daemon_log("ERROR: incoming msg cannot be decrypted with server passwd", 1); return; } my $header = @{$msg_hash->{header}}[0]; daemon_log("receive '$header' from $host", 1); #check whether msg to process is a event opendir(DIR, $event_dir) or daemon_log("cannot find directory $event_dir, no events specified", 5); my $file_name; while(defined($file_name = readdir(DIR))){ if ($file_name eq "." || $file_name eq "..") { next; } if ($file_name eq $header) { my $cmd = "$event_dir/$file_name '$msg'"; my $result_xml = ""; open(PIPE, "$cmd 2>&1 |"); while() { $result_xml.=$_; last; } close(PIPE); my $res_hash = &transform_msg2hash($result_xml); my $res_target = @{$res_hash->{target}}[0]; &send_msg_hash2address($res_hash, $server_address); return; } } close(DIR); daemon_log("could not assign the msg $header to an event", 5); if ($header eq 'new_ldap_config') { if ($ldap_enabled == 1) {&new_ldap_config($msg_hash)}} elsif ($header eq 'ping') { &got_ping($msg_hash) } elsif ($header eq 'wake_up') { &execute_event($msg_hash)} elsif ($header eq 'new_passwd') { &new_passwd()} elsif ($header eq 'detect_hardware') { &detect_hardware()} else { daemon_log("ERROR: no function assigned to msg $header", 5) } return; } #=== FUNCTION ================================================================ # NAME: # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub update_status { my ($new_status) = @_ ; my $out_hash = &create_xml_hash("update_status", $client_address, $server_address); &add_content2xml_hash($out_hash, "update_status", $new_status); &send_msg_hash2address($out_hash, $server_address); return; } #=== FUNCTION ================================================================ # NAME: # PARAMETERS: # RETURNS: # DESCRIPTION: #=============================================================================== sub server_leaving { my ($msg_hash) = @_ ; my $source = &get_content_from_xml_hash("source"); my $header = &get_content_from_xml_hash("header"); daemon_log("gosa daemon $source is going down, cause registration procedure", 1); my $server_address = "none"; my $server_passwd = "none"; my $server_cipher = "none"; # reinitialization of default values in config file &read_configfile; # registrated at new daemon ®ister_at_server(); return; } sub got_ping { my ($msg_hash) = @_ ; my $source = &get_content_from_xml_hash($msg_hash, 'source'); my $target = &get_content_from_xml_hash($msg_hash, 'target'); my $header = &get_content_from_xml_hash($msg_hash, 'header'); &add_content2known_hosts(hostname=>$target, status=>$header); my $out_hash = &create_xml_hash("got_ping", $target, $source); &send_msg_hash2address($out_hash, $source, $server_passwd); return; } sub new_ldap_config { my ($msg_hash) = @_ ; my $element; my @ldap_uris; my $ldap_base; my @ldap_options; my @pam_options; my @nss_options; my $goto_admin; my $goto_secret; my $admin_base= ""; my $department= ""; my $unit_tag; # Transform input into array while ( my ($key, $value) = each(%$msg_hash) ) { if ($key =~ /^(source|target|header)$/) { next; } foreach $element (@$value) { if ($key =~ /^ldap_uri$/) { push (@ldap_uris, $element); next; } if ($key =~ /^ldap_base$/) { $ldap_base= $element; next; } if ($key =~ /^goto_admin$/) { $goto_admin= $element; next; } if ($key =~ /^goto_secret$/) { $goto_secret= $element; next; } if ($key =~ /^ldap_cfg$/) { push (@ldap_options, "$element"); next; } if ($key =~ /^pam_cfg$/) { push (@pam_options, "$element"); next; } if ($key =~ /^nss_cfg$/) { push (@nss_options, "$element"); next; } if ($key =~ /^admin_base$/) { $admin_base= $element; next; } if ($key =~ /^department$/) { $department= $element; next; } if ($key =~ /^unit_tag$/) { $unit_tag= $element; next; } } } # Unit tagging enabled? if (defined $unit_tag){ push (@pam_options, "pam_filter gosaUnitTag=$unit_tag"); push (@nss_options, "nss_base_passwd $admin_base?sub?gosaUnitTag=$unit_tag"); push (@nss_options, "nss_base_group $admin_base?sub?gosaUnitTag=$unit_tag"); } # Setup ldap.conf my $file1; my $file2; open(file1, "> $ldap_config"); print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n"; print file1 "URI"; foreach $element (@ldap_uris) { print file1 " $element"; } print file1 "\nBASE $ldap_base\n"; foreach $element (@ldap_options) { print file1 "$element\n"; } close (file1); daemon_log("wrote $ldap_config", 5); # Setup pam_ldap.conf / libnss_ldap.conf open(file1, "> $pam_config"); open(file2, "> $nss_config"); print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n"; print file2 "# This file was automatically generated by gosa-si-client. Do not change.\n"; print file1 "uri"; print file2 "uri"; foreach $element (@ldap_uris) { print file1 " $element"; print file2 " $element"; } print file1 "\nbase $ldap_base\n"; print file2 "\nbase $ldap_base\n"; foreach $element (@pam_options) { print file1 "$element\n"; } foreach $element (@nss_options) { print file2 "$element\n"; } close (file2); daemon_log("wrote $nss_config", 5); close (file1); daemon_log("wrote $pam_config", 5); # Create goto.secrets if told so - for compatibility reasons if (defined $goto_admin){ open(file1, "> /etc/goto/secret"); close(file1); chown(0,0, "/etc/goto/secret"); chmod(0600, "/etc/goto/secret"); open(file1, "> /etc/goto/secret"); print file1 "GOTOADMIN=\"$goto_admin\"\nGOTOSECRET=\"$goto_secret\"\n"; close(file1); daemon_log("wrote /etc/goto/secret", 5); } # Write shell based config my $cfg_name= dirname($ldap_config)."/ldap-shell.conf"; open(file1, "> $cfg_name"); print file1 "LDAP_BASE=\"$ldap_base\"\n"; print file1 "ADMIN_BASE=\"$admin_base\"\n"; print file1 "DEPARTMENT=\"$department\"\n"; print file1 "UNIT_TAG=\"".(defined $unit_tag ? "$unit_tag" : "")."\"\n"; print file1 "UNIT_TAG_FILTER=\"".(defined $unit_tag ? "(gosaUnitTag=$unit_tag)" : "")."\"\n"; close(file1); daemon_log("wrote $cfg_name", 5); return; } sub execute_event { my ($msg_hash)= @_; my $configdir= '/etc/gosa-si/client/events/'; my $result; my $header = &get_content_from_xml_hash($msg_hash, 'header'); my $source = &get_content_from_xml_hash($msg_hash, 'source'); my $target = &get_content_from_xml_hash($msg_hash, 'target'); if((not defined $source) && (not defined $target) && (not defined $header)) { daemon_log("ERROR: Entries missing in XML msg for gosa events under $configdir"); } else { my $parameters=""; my @params = &get_content_from_xml_hash($msg_hash, $header); my $params = join(", ", @params); daemon_log("execute_event: got parameters: $params", 5); if (@params) { foreach my $param (@params) { my $param_value = (&get_content_from_xml_hash($msg_hash, $param))[0]; daemon_log("execute_event: parameter -> value: $param -> $param_value", 7); $parameters.= " ".$param_value; } } my $cmd= $configdir.$header."$parameters"; daemon_log("execute_event: executing cmd: $cmd", 7); $result= ""; open(PIPE, "$cmd 2>&1 |"); while() { $result.=$_; } close(PIPE); } # process the event result return; } sub new_passwd { # my ($msg_hash) = @_ ; my $new_server_passwd = &create_passwd(); my $new_server_cipher = &create_ciphering($new_server_passwd); my $out_hash = &create_xml_hash("new_passwd", $client_address, $server_address, $new_server_passwd); &send_msg_hash2address($out_hash, $server_address, $server_passwd); $server_passwd = $new_server_passwd; $server_cipher = $new_server_cipher; return; } sub generate_hw_digest { my $hw_data; foreach my $line (split /\n/, `cat /proc/bus/pci/devices`) { $hw_data.= sprintf "%s", $line =~ /[^\s]+\s([^\s]+)\s.*/; } return(md5_base64($hw_data)); } sub detect_hardware { my $hwinfo= `which hwinfo`; chomp $hwinfo; if (!(defined($hwinfo) && length($hwinfo) > 0)) { &main::daemon_log("ERROR: hwinfo was not found in \$PATH! Hardware detection will not work!", 1); return; } my $result= { gotoXDriver => "", gotoXMouseType => "", gotoXMouseport => "", gotoXkbModel => "", gotoXHsync => "", gotoXVsync => "", gotoXResolution => "", ghUsbSupport => "", gotoSndModule => "", ghGfxAdapter => "", ghNetNic => "", ghSoundAdapter => "", ghMemSize => "", ghCpuType => "", gotoModules => [], ghIdeDev => [], ghScsiDev => [], }; &main::daemon_log("Starting hardware detection", 4); my $gfxcard= `$hwinfo --gfxcard`; my $primary_adapter= $1 if $gfxcard =~ /^Primary display adapter:\s#(\d+)\n/m; if(defined($primary_adapter)) { ($result->{ghGfxAdapter}, $result->{gotoXDriver}) = ($1,$2) if $gfxcard =~ /$primary_adapter:.*?Model:\s\"([^\"]*)\".*?Server Module:\s(\w*).*?\n\n/s; } my $monitor= `$hwinfo --monitor`; my $primary_monitor= $1 if $monitor =~ /^(\d*):.*/m; if(defined($primary_monitor)) { ($result->{gotoXResolution}, $result->{gotoXVsync}, $result->{gotoXHsync})= ($1,$2,$3) if $monitor =~ /$primary_monitor:\s.*?Max\.\sResolution:\s([0-9x]*).*?Vert\.\sSync\sRange:\s([\d\-]*)\sHz.*?Hor\.\sSync\sRange:\s([\d\-]*)\skHz.*/s; } if(length($result->{gotoXHsync}) == 0) { # set default values $result->{gotoXHsync} = "30+50"; $result->{gotoXVsync} = "30+90"; } my $mouse= `$hwinfo --mouse`; my $primary_mouse= $1 if $mouse =~ /^(\d*):.*/m; if(defined($primary_mouse)) { ($result->{gotoXMouseport}, $result->{gotoXMouseType}) = ($1,$2) if $mouse =~ /$primary_mouse:\s.*?Device\sFile:\s(.*?)\s.*?XFree86\sProtocol:\s(.*?)\n.*?/s; } my $sound= `$hwinfo --sound`; my $primary_sound= $1 if $sound =~ /^(\d*):.*/m; if(defined($primary_sound)) { ($result->{ghSoundAdapter}, $result->{gotoSndModule})= ($1,$2) if $sound =~ /$primary_sound:\s.*?Model:\s\"(.*?)\".*?Driver\sModules:\s\"(.*?)\".*/s; } my $netcard= `hwinfo --netcard`; my $primary_netcard= $1 if $netcard =~ /^(\d*):.*/m; if(defined($primary_netcard)) { $result->{ghNetNic}= $1 if $netcard =~ /$primary_netcard:\s.*?Model:\s\"(.*?)\".*/s; } my $keyboard= `hwinfo --keyboard`; my $primary_keyboard= $1 if $keyboard =~ /^(\d*):.*/m; if(defined($primary_keyboard)) { $result->{gotoXkbModel}= $1 if $keyboard =~ /$primary_keyboard:\s.*?XkbModel:\s(.*?)\n.*/s; } $result->{ghCpuType}= sprintf "%s / %s - %s", `cat /proc/cpuinfo` =~ /.*?vendor_id\s+:\s(.*?)\n.*?model\sname\s+:\s(.*?)\n.*?cpu\sMHz\s+:\s(.*?)\n.*/s; $result->{ghMemSize}= $1 if `cat /proc/meminfo` =~ /^MemTotal:\s+(.*?)\skB.*/s; my @gotoModules=(); for my $line(`lsmod`) { if (($line =~ /^Module.*$/) or ($line =~ /^snd.*$/)) { next; } else { push @gotoModules, $1 if $line =~ /^(\w*).*$/ } } my %seen = (); # Remove duplicates and save push @{$result->{gotoModules}}, grep { ! $seen{$_} ++ } @gotoModules; $result->{ghUsbSupport} = (-d "/proc/bus/usb")?"true":"false"; #TODO Ide detection #$result->{ghIdeDev} = $@ if `` #opendir(IDE, "/proc/ide"); #for my $model(grep /ide\d\/hd\w\/model/, readdir(DIR)) { # print "$model\n"; #} #close(IDE); while ( `cat /proc/scsi/scsi` =~ /^.*?Vendor:\s(.*?)\s+Model:\s(.*?)\s+.*$/mg ) { push (@{$result->{ghScsiDev}}, "$1 $2"); } &main::daemon_log("Hardware detection done!", 4); return &send_msg_hash2address( &create_xml_hash("detected_hardware", $client_address, $server_address, $result), $server_address, $server_passwd ); } #==== MAIN = main ============================================================== # parse commandline options Getopt::Long::Configure( "bundling" ); GetOptions("h|help" => \&usage, "c|config=s" => \$cfg_file, "f|foreground" => \$foreground, "v|verbose+" => \$verbose, ); # read and set config parameters &check_cmdline_param ; &read_configfile; &check_pid; if ( ! $foreground ) { open STDIN, '/dev/null' or die "Can’t read /dev/null: $!"; open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!"; open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!"; } # restart daemon log file if(-e $log_file ) { unlink $log_file } daemon_log(" ", 1); daemon_log("$0 started!", 1); # Just fork, if we"re not in foreground mode if( ! $foreground ) { $pid = fork(); } else { $pid = $$; } # Do something useful - put our PID into the pid_file if( 0 != $pid ) { open( LOCK_FILE, ">$pid_file" ); print LOCK_FILE "$pid\n"; close( LOCK_FILE ); if( !$foreground ) { exit( 0 ) }; } # detect own ip and mac address $network_interface= &get_interface_for_ip($client_ip); $client_mac_address= &get_mac($network_interface); # ($client_ip, $client_mac_address) = &get_ip_and_mac(); #if (not defined $client_ip) { # die "EXIT: ip address of $0 could not be detected"; #} daemon_log("client ip address detected: $client_ip", 1); daemon_log("client mac address detected: $client_mac_address", 1); # prepare variables if (defined $server_ip && defined $server_port) { $server_address = $server_ip.":".$server_port; } # this is necessary that gosa-si-server knowns to which ip-address he can send msgs if( $client_ip eq "0.0.0.0" ) { $client_ip = "127.0.0.1"; } $client_address = $client_ip.":".$client_port; # setup xml parser $xml = new XML::Simple(); # compute hardware checksum $gotoHardwareChecksum= &generate_hw_digest(); # create input socket daemon_log(" ", 1); $rbits = $wbits = $ebits = ""; $input_socket = IO::Socket::INET->new(LocalPort => $client_port, Type => SOCK_STREAM, Reuse => 1, Listen => 20, ); if(not defined $input_socket){ daemon_log("cannot be a tcp server at $client_port : $@\n"); } else { daemon_log("start client at $client_address",1) ; vec($rbits, fileno $input_socket, 1) = 1; vec($wbits, fileno $input_socket, 1) = 1; } # register at server daemon_log(" ", 1); ®ister_at_server(); ############## # Debugging ############# #sleep(2); #&update_status("ich_bin_ein_neuer_status"); ################################### #everything ready, okay, lets start ################################### while(1) { my ($rout, $wout); my $nf = select($rout=$rbits, $wout=$wbits, undef, undef); # error handling if($nf < 0 ) { } # something is coming in if(vec $rout, fileno $input_socket, 1) { my $client = $input_socket->accept(); my $other_end = getpeername($client); if(not defined $other_end) { daemon_log("client cannot be identified: $!"); } else { my ($port, $iaddr) = unpack_sockaddr_in($other_end); my $actual_ip = inet_ntoa($iaddr); daemon_log("accept client from $actual_ip", 5); my $in_msg = &read_from_socket($client); if(defined $in_msg){ chomp($in_msg); $in_msg = $in_msg.".".$actual_ip; &process_incoming_msg($in_msg); } } } }