Dienstag, 30. Juli 2013

Wie überwache ich Fortinet FortiGate Appliances

Da wir hier im Hause diverse Fortinet FortiGate, teilweise als Active/Passive Cluster, betreiben, liegt es natürlich nahe, dass diese Appliances ebenfalls mit in die Überwachung aufgenommen werden.

Kurzer Exkurs:
Die bei uns eingesetzten Fortinet FortiGates gehören zur Klasse der UTM Geräte (Unified Threat Management) und bieten unter anderem folgende Features:
  • IPS - Intrusion Prevention
  • AntiVirus - Automatisierte Überprüfung auf Viren für z.B: HTTP, SMTP, POP3, IMAP, FTP
  • VPN - IPSec / SSL, inkl. 2-Faktor Authentifizierung
  • Proxy - inkl. Web-Filtering, SSO über LDAP/Radius bzw. SSO Client auf DC
  • Deep Packet Inspection
  • Firewall
  • Routing
  • IPv4 & IPv6

Da mir die existierenden Fortinet Checks nicht wirklich gefallen haben (Code-Qualität, Art der Aufrufe, Funktionsumfang), habe ich mich entschieden ein eigenes Plugin in Perl zu schreiben (Download auf exchange.nagios.org):

Zum jetztigen Stand unterstützt es:
  • CPU
  • Memory
  • Sessions
  • VPN
  • Cluster-Zustand

Beschreibung:


NAME
Check Fortinet FortiGate Appliances

SYNOPSIS
check_fortigate.pl -H -C -T [-w|-c|-S|-s|-R|-M|-V|-?]
Options:

-H --host STRING or IPADDRESS Check interface on the indicated host
-C --community STRING Community-String for SNMP
-T -- type STRING CPU, MEM, Ses, VPN, Cluster
-S --serial STRING Primary serial number
-s --slave get values of slave
-w --warning INTEGER Warning threshold, applies to cpu, mem, session.
-c --critical INTEGER Critical threshold, applies to cpu, mem, session.
-R --reset Resets ip file (cluster only)
-M --mode STRING Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical
-V --vpnmode STRING VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only
-? --help Returns full help text

OPTIONS
-H--host
STRING or IPADDRESS - Check interface on the indicated host.

-C|--community
STRING - Community-String for SNMP

-T|--type
STRING - CPU, MEM, Ses, VPN, Cluster

-S|--serial
STRING - Primary serial number.

-s|--slave
BOOL - Get values of slave

-w|--warning
INTEGER - Warning threshold, applies to cpu, mem, session.

-c|--critical
INTEGER - Critical threshold, applies to cpu, mem, session.

-R|--reset
BOOL - Resets ip file (cluster only)

-M|--mode
STRING - Output-Mode: 0 => just print, 1 => print and show
failed tunnel, 2 => critical

-V|--vpnmode
STRING - VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec
only, ssl => SSL/OpenVPN only


Der Code:

#!/usr/bin/perl
# This  Plugin checks the cluster state of FortiGate
# Tested on: FortiGate 100D / FortiGate 300C (both 5.0.3) 
#
# Author: Oliver Skibbe (oliskibbe (at) gmail.com)
# Date: 2013-07-29
#
# Changelog:
#    - initial release (cluster, cpu, memory, session support)
#    - added vpn support, based on check_fortigate_vpn.pl: Copyright (c) 2009 Gerrit Doornenbal, g(dot)doornenbal(at)hccnet(dot)nl
#
# This program is free software; you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation; either version 2 
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
# GNU General Public License for more details.
#
# If you wish to receive a copy of the GNU General Public License, 
# write to the Free Software Foundation, Inc., 
# 59 Temple Place - Suite 330, Boston, MA 02111-130
# Description:

use strict;
use Net::SNMP;
use List::Compare;
use Switch;
use Getopt::Long qw(:config no_ignore_case bundling);
use Pod::Usage;

# Parse out the arguments...
my ($ip, $community, $type, $warn, $crit, $slave, $pri_serial, $reset_file, $mode, $vpnmode) = parse_args();

# Initialize variables....
my $net_snmp_debug_level = 0x0;                    # See http://search.cpan.org/~dtown/Net-SNMP-v6.0.1/lib/Net/SNMP.pm#debug()_-_set_or_get_the_debug_mode_for_the_module
                                # for more information.
my %status = (    'UNKNOWN'  => '-1',                # Enumeration for the output Nagios states
                'OK'       => '0',
                'WARNING'  => '1',
                'CRITICAL' => '2' );

## SNMP ##
my ($session, $error) = get_snmp_session($ip, $community);    # Open an SNMP connection...

## OIDs ##
my $oid_unitdesc = ".1.3.6.1.2.1.1.1.0";            # Location of Fortinet device description... (String)
my $oid_serial = ".1.3.6.1.2.1.1.5.0";                # Location of Fortinet serial number (String)
my $oid_cluster_type = ".1.3.6.1.4.1.12356.101.13.1.1.0";    # Location of Fortinet serial number (String)
my $oid_cluster_serials = ".1.3.6.1.4.1.12356.101.13.2.1.1.2";    # Location of Cluster serials (String)
# Generic my $oid_cpu = ".1.3.6.1.4.1.12356.101.4.1.3.0";    # Location of CPU (%)
my $oid_mem = ".1.3.6.1.4.1.12356.101.13.2.1.1.4";        # Location of cluster member Mem (%)
# Generic my $oid_mem = ".1.3.6.1.4.1.12356.101.4.1.4.0";    # Location of Mem (%)
my $oid_cpu = ".1.3.6.1.4.1.12356.101.13.2.1.1.1";        # Location of cluster member CPU (%)
# Generic my $oid_ses = ".1.3.6.1.4.1.12356.101.4.1.8.0";    # Location of Sessions (int)
my $oid_ses = ".1.3.6.1.4.1.12356.101.13.2.1.1.6";        # Location of cluster member Sessions (int)

# VPN OIDs
my $oid_ActiveSSL = ".1.3.6.1.4.1.12356.101.12.2.3.1.2.1";              # Location of Fortinet firewall SSL VPN Tunnel connection count
my $oid_ActiveSSLTunnel = ".1.3.6.1.4.1.12356.101.12.2.3.1.6.1";        # Location of Fortinet firewall SSL VPN Tunnel connection count
my $oid_ipsectuntableroot = ".1.3.6.1.4.1.12356.101.12.2.2.1";          # Table of IPSec VPN tunnels
my $oidf_tunstatus = ".20";                                             # Location of a tunnel's connection status
my $oidf_tunndx = ".1";                                                 # Location of a tunnel's index...
my $oidf_tunname = ".3";                                                # Location of a tunnel's name...

## Stuff ##

my $state;                            # return state
my $path = "/usr/lib/nagios/plugins/FortiSerial";            # path to store serial filenames
my $filename = $path . "/" . $ip;                # file name to store serials
my $oid;                            # helper var
my $value;                            # helper var
my $string;                            # return string
my $perf;                            # performance data

# Check SNMP connection and get the description of the device...
my $curr_device  = get_snmp_value($session, $oid_unitdesc);
# Check SNMP connection and get the serial of the device...
my $curr_serial  = get_snmp_value($session, $oid_serial);

switch ( lc($type) ) {
    case "cpu" { ($state, $string) = get_health_value($oid_cpu, "CPU", "%"); }
    case "mem" { ($state, $string) = get_health_value($oid_mem, "Memory", "%"); }
    case "ses" { ($state, $string) = get_health_value($oid_ses, "Session", ""); }
    case "vpn" { ($state, $string) = get_vpn_state(); }
    else { ($state, $string) = get_cluster_state(); }
}

# Close the connection
close_snmp_session($session);  

# exit with a return code matching the state...
print $string."\n";
exit($status{$state});

########################################################################
##  Subroutines below here....
########################################################################
sub get_snmp_session{
  my $ip        = $_[0];
  my $community = $_[1];
  my ($session, $error) = Net::SNMP->session(
             -hostname  => $ip,
             -community => $community,
             -port      => 161,
             -timeout   => 5,
             -retries   => 3,
             -debug        => $net_snmp_debug_level,
             -version    => 2,
             -translate => [-timeticks => 0x0] #schaltet Umwandlung von Timeticks in Zeitformat aus
              );
  return ($session, $error);
} # end get snmp session

sub get_health_value {

    my $label = $_[1];
    my $UOM = $_[2];

    if ( $slave == 1 ) {
        $oid = $_[0] . ".2";
        $label = "slave_" . $label;
    } else {
        $oid = $_[0] . ".1";
    }

    $value = get_snmp_value($session, $oid);

    if ( $value > $crit ) {
        $state = "CRITICAL";
        $string = $label . " is critical: " . $value . $UOM; 
    } elsif ( $value > $warn ) {
        $state = "WARNING";
        $string = $label . " is warning: " . $value . $UOM;
    } else {
        $state = "OK";
        $string = $label . " is okay: " . $value. $UOM;
    }

    $perf = "|'" . lc($label) . "'=" . $value . $UOM . ";" . $warn . ";" . $crit;
    $string = $state . ": " . $curr_device . " (Master: " . $curr_serial .") " . $string . $perf;
    return ($state, $string);

} # end health value

sub get_cluster_state {

    my @help_serials;                        # helper array

    # get all cluster member serials
    my %snmp_serials = %{get_snmp_table($session, $oid_cluster_serials)};
    my $cluster_type = get_snmp_value($session, $oid_cluster_type);

    my %cluster_types = (1 => "Standalone", 2 => "Active/Active", 3 => "Active/Passive");

    # first time, write cluster members to helper file
    if ( ! -e $filename || $reset_file ) {
            # open file handle to write (create/truncate)
            open (SERIALHANDLE,"+>$filename") || die "Error while creating $filename";
    
            # write serials to file
            while (($oid, $value) = each (%snmp_serials)) {
                    print (SERIALHANDLE $value . "\n");
            }
    }

    # snmp serials
    while (($oid, $value) = each (%snmp_serials)) {
            chomp;                          # remove "\n" if exists
            push @help_serials, $value;
    }

    # if less then 2 nodes found: critical
    if ( scalar(@help_serials) < 2 ) {
            $string = "HA (" . $cluster_types{$cluster_type} . ") inactive, single node found: " . $curr_serial;
            $state = "CRITICAL";
    # else check if there are differences in ha nodes
    } else {
            # open existing serials
            open ( SERIALHANDLE, "$filename") || die "Error while opening file $filename";
            my @file_serials = <SERIALHANDLE>;                      # push lines into file_serials
            chomp(@file_serials);                           # remove "\n" if exists in array elements
            close (SERIALHANDLE);                           # close file handle
    
        
            # compare serial arrays
            my $comparedList = List::Compare->new('--unsorted', \@help_serials, \@file_serials);
    
            if ( $comparedList->is_LequivalentR ) {
                    $string = "HA (" . $cluster_types{$cluster_type} . ") is active";
                    $state = "OK";
            } else {
                    $string = "Unknown node in active HA (" . $cluster_types{$cluster_type} . ") found";
                    $state = "WARNING";
            }
    } # end scalar count
    
    # if preferred master serial is not master
    if ( $pri_serial && ( $pri_serial ne $curr_serial ) ) {
            $string = $string . ", preferred master " . $pri_serial . " is not master!";
            $state = "CRITICAL";
    }

    # Write an output string...
    $string = $state . ": " . $curr_device . " (Master: " . $curr_serial . ", Slave: " . @help_serials[$#help_serials] . "): " . $string;

    return ($state, $string);
} # end cluster state

sub get_vpn_state {

    my $ipstunsdown = 0;
    my $ipstuncount = 0;
    my $ipstunsopen = 0;
    my $ActiveSSL = 0;
    my $ActiveSSLTunnel = 0;
    my $string_errors = "";
    my %entitystate = (     '1' => 'down',                          # Enumeration for the tunnel up/down states
                            '2' => 'up' );
    $state = "OK";

    # Unless specifically requesting IPSec checks only, do an SSL connection check
    if ($vpnmode ne "ipsec"){
        $ActiveSSL = get_snmp_value($session, $oid_ActiveSSL);
        $ActiveSSLTunnel = get_snmp_value($session, $oid_ActiveSSLTunnel);
    }

    # Unless specifically requesting SSL checks only, do an IPSec tunnel check
    if ($vpnmode ne "ssl"){
        # Get just the top level tunnel data
        my %tunnels = %{get_snmp_table($session, $oid_ipsectuntableroot . $oidf_tunndx)};
        while (($oid, $value) = each (%tunnels)) {
            #Bump the total tunnel count
            $ipstuncount++;
            
            #print "Tunnel name (" . $oid_ipsectuntableroot . $oidf_tunname . "." . $ipstuncount . ") is: " . get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunname . "." . $ipstuncount) . "\n";
            #print "Tunnel status (" . $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount . ") is: " . get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount) . "\n";

            #If the tunnel is up, bump the connected tunnel count
            if ( $entitystate{get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount)} eq "up" )
            {
                $ipstunsopen++;
            } else {
                #Tunnel is down.  Add it to the failed counter
                $ipstunsdown++;
                # If we're counting failures and/or monitoring, put together an output error string of the tunnel name and its status
                if ($mode >= 1){
                    $string_errors .= ", ";
                    $string_errors .= get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunname . "." . $ipstuncount)." ".$entitystate{get_snmp_value($session, $oid_ipsectuntableroot . $oidf_tunstatus . "." . $ipstuncount)};
                }
            }
        }
    }

    #Set Unitstate
    my $unitstate="OK";
    if (($mode >= 2 ) && ($vpnmode ne "ssl")) {
        if ($ipstunsdown == 1) { $unitstate="WARNING"; }
        if ($ipstunsdown >= 2) { $unitstate="CRITICAL";    }
    }

    # Write an output string...
    $string = $unitstate . ": " .  $curr_device . " (Master: " . $curr_serial .")";

    if ($vpnmode ne "ipsec") {
            #Add the SSL tunnel count
            $string = $string . ": Active SSL-VPN Connections/Tunnels: " . $ActiveSSL."/".$ActiveSSLTunnel."";
    }
    if ($vpnmode ne "ssl") {
            #Add the IPSec tunnel count and any errors....
            $string = $string . ": IPSEC Tunnels: Configured/Active: " . $ipstuncount . "/" . $ipstunsopen. " " . $string_errors;
    }

    # Create performance data
    $perf="|'ActiveSSL-VPN'=".$ActiveSSL." 'ActiveIPSEC'=".$ipstunsopen;

    $string = $string.$perf;

    # Check to see if the output string contains either "unkw", "WARNING" or "down", and set an output state accordingly...
    if($string =~/uknw/){
            $state = "UNKNOWN";
    }
    if($string =~/WARNING/){
            $state = "WARNING";
    }
    if($string =~/down/){
            $state = "CRITICAL";
    }

    return ($state, $string);

} # end vpn state


sub close_snmp_session{
  my $session = $_[0];
  
  $session->close();
} # end close snmp session

sub get_snmp_value{
    my $session = $_[0];
    my $oid     = $_[1];
    my (%result) = %{get_snmp_request($session, $oid) or die ("SNMP service is not available on ".$ip) }; 
    return $result{$oid};
} # end get snmp value

sub get_snmp_request{
  my $session = $_[0];
  my $oid     = $_[1];
  return $session->get_request($oid) || die ("SNMP service not responding");
} # end get snmp request

sub get_snmp_table{
    my $session = $_[0];
    my $oid     = $_[1];
    return $session->get_table(    
            -baseoid =>$oid
            ); 
} # end get snmp table


sub parse_args
{
    my $ip = "";
    my $community = "public";
    my $pri_serial = "";
    my $reset_file = "";
    my $type = "status";
    my $warn = 80;
    my $crit = 90;
    my $slave = 0;
    my $vpnmode = "both";
    my $mode = 2;
    my $help = 0;

    pod2usage(-message => "UNKNOWN: No Arguments given", -exitval => 3, -verbose => 0) if ( !@ARGV );

    GetOptions(
        'host|H=s'    => \$ip,
        'type|T=s'    => \$type,
        'community|C:s' => \$community,
        'serial|S:s'    => \$pri_serial,
        'vpnmode|V:s'    => \$vpnmode,
        'mode|M:s'    => \$mode,
        'warning|w:i'    => \$warn,
        'critical|c:i'    => \$crit,
        'slave|s:1'    => \$slave,
        'reset|R:1'    => \$reset_file,
        'help|?!'    => \$help,
    ) or pod2usage(-exitval => 3, -verbose => 0);

    pod2usage(-exitval => 3, -verbose => 2) if $help;

      return ($ip, $community, $type, $warn, $crit, $slave, $pri_serial, $reset_file, $mode, $vpnmode); 
}        

__END__

=head1 NAME

Check Fortinet FortiGate Appliances

=head1 SYNOPSIS

=item S<check_fortigate.pl -H -C -T [-w|-c|-S|-s|-R|-M|-V|-?]>

Options:

    -H --host STRING or IPADDRESS    Check interface on the indicated host
    -C --community STRING Community-String for SNMP
    -T -- type STRING CPU, MEM, Ses, VPN, Cluster
    -S --serial STRING Primary serial number
    -s --slave get values of slave
    -w --warning INTEGER Warning threshold, applies to cpu, mem, session. 
    -c --critical INTEGER Critical threshold,  applies to cpu, mem, session. 
    -R --reset Resets ip file (cluster only)
    -M --mode STRING Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical 
    -V --vpnmode STRING VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only
    -? --help Returns full help text

    
=head1 OPTIONS

=over 8
    
=item B<-H--host>

STRING or IPADDRESS - Check interface on the indicated host.

=item B<-C|--community>

STRING - Community-String for SNMP
    
=item B<-T|--type>

STRING - CPU, MEM, Ses, VPN, Cluster

=item B<-S|--serial>

STRING - Primary serial number. 

=item B<-s|--slave>

BOOL - Get values of slave

=item B<-w|--warning>

INTEGER - Warning threshold, applies to cpu, mem, session. 
    
=item B<-c|--critical>

INTEGER - Critical threshold, applies to cpu, mem, session. 

=item B<-R|--reset>

BOOL - Resets ip file (cluster only)

=item B<-M|--mode>

STRING - Output-Mode: 0 => just print, 1 => print and show failed tunnel, 2 => critical 

=item B<-V|--vpnmode>

STRING - VPN-Mode: both => IPSec & SSL/OpenVPN, ipsec => IPSec only, ssl => SSL/OpenVPN only

=back

=head1 DESCRIPTION
    
This plugin checks Fortinet FortiGate devices via SNMP 

=head2 From Web: 

=item 1. Select Network -> Interface -> Local interface

=item 2. Administrative Access: Enable SNMP

=item 3. Select Config -> SNMP

=item 4. Enable SNMP, fill your details

=item 5. SNMP v1/v2c: Create new

=item 6. Configure for your needs, Traps are not required for this plugin!

=head2 From CLI:

 config system interface
    edit "internal"
        set allowaccess ping https ssh snmp fgfm
    next
 end

 config system snmp sysinfo
    set description "DMZ1 FortiGate 300C"
    set location "Room 404"
    set conctact-info "BOFH"
    set status enable
 end


 config system snmp community
   edit 1
      set events cpu-high mem-low fm-if-change
           config hosts
                edit 1
                     set interface "internal"
                     set ip %SNMP Client IP%
                next
           end
      set name "public"
      set trap-v1-status disable
      set trap-v2c-status disable
   next
 end


Thats it!

=cut


Auf den Geräten muss natürlich SNMP aktiviert werden:

Via Web:
  1. Network -> Interface -> Interface, welches auf SNMP lauschen soll, auswählen. Achtung, nicht auf das externe Interface freigeben!
  2. Administrative Access: Häckchen bei SNMP setzen
  3. Config -> SNMP auswählen
  4. Häckchen bei SNMP
  5. SNMP v1/v2c: Create new
  6. Traps werden nicht benötigt, Rest kann nach eigenem Willen konfiguriert werden
Via CLI:
  1.  Login via SSH oder Web-CLI

     config system interface
        edit "internal"
            set allowaccess ping https ssh snmp fgfm
        next
     end

     config system snmp sysinfo
        set description "DMZ1 FortiGate 300C"
        set location "Room 404"
        set conctact-info "BOFH"
        set status enable
     end

     config system snmp community
       edit 1
          set events cpu-high mem-low fm-if-change
               config hosts
                    edit 1
                         set interface "internal"
                         set ip %SNMP Client IP%
                    next
               end
          set name "public"
          set trap-v1-status disable
          set trap-v2c-status disable
       next
     end

Damit das Plugin auch schön in Nagios eingebunden werden kann, hier noch die commands.cfg.

Achtung, ich nutze hier Host Variablen http://nagios.sourceforge.net/docs/3_0/customobjectvars.html

commands.cfg:

define command {
        command_name    check_fortigate_cpu
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cpu
}

define command {
        command_name    check_fortigate_slave_cpu
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cpu -s
}

define command {
        command_name    check_fortigate_mem
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T mem
}

define command {
        command_name    check_fortigate_slave_mem
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T mem -s
}

define command {
        command_name    check_fortigate_cluster
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cluster
}

define command {
        command_name    check_fortigate_pri_cluster
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T cluster -S $ARG1$
}

define command {
        command_name    check_fortigate_ses
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T ses -w 250 -c 350
}

define command {
        command_name    check_fortigate_slave_ses
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T ses -s -w 250 -c 350
}

define command {
        command_name    check_fortigate_vpn
        command_line    $USER1$/check_fortigate.pl -H $HOSTADDRESS$ -C $_HOSTSNMP_COMMUNITY$ -T vpn -V $ARG1$ -M 0
}


Die ganzen Sachen gibt es natürlich auch direkt zum Download über exchange.nagios.org 


Bei Fragen bitte wie immer melden.



Mal sehen, was die Recherche zu Juniper Plugins betrifft, ggf. wird der nächste Eintrag über dieses Thema sein :-)

Mittwoch, 24. Juli 2013

Wie löscht man sicher, effizient und automatisiert Daten

Bei meiner neuen Stelle schnappt man auch viele Dinge nebenbei auf, die verbessert werden wollen.

Ein Beispiel ist das sichere Löschen von Notebooks / PCs / Servern zum Leasing-Ende, damit auf keinen Fall sensible Daten das Unternehmen verlassen und später bei Ebay landen können.

Bisher wurden die Geräte händisch mit einer DBAN (Darik's Boot and Nuke) CD einzeln gestartet und anschließend mit manueller Bestätigung gelöscht.
Als Löschmethode kam dabei DoD 5220.22-M Full zum Einsatz

Kurzer Exkurs zu DBAN:
DBAN unterstützt natürlich noch mehr Methoden:
  • Quick Erase: 1x mit "0" überschreiben, eigentlich nur bei Virenbefall empfehlenswert
  • RCMP TSSIT OPS-II: 8x mit Zufallswerten überschreiben, geht allerdings
  • DoD Short (5220.22-M): Schritt 1 (mit "0" überschreiben), 2 (mit "1" überschreiben) und 7 (mit "Zufalls"werten überschreiben) des "vollen" DoD  5220.22-M
  • DoD Full (5220.22-M): 7x überschreiben
  • Gutmann: 35x Durchläufe (http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html), wobei der Erfinder mittlerweile selbst gesagt, dass diese Variante über das Ziel hinaus schießt. 
  • PRNG: Pseudo Random Number Generator, wie der Name sagt, wird die Festplatte mit "Zufalls"-zahlen (bei heutigen Rechenwerke gibt es keine echte Zufälligkeit) überschrieben. Die Anzahl der Durchläufe kann angegeben werden.
Meine Empfehlung geht bei weniger sensiblen Daten zu DoD Short / PRNG mit 2-3 Durchläufen und bei sensibleren Daten zu DoD Full / PRNG 4-6 Durchläufe.

Je nach Auswahl der Löschmethode und Größe der Festplatten kann das Löschen der Geräte lange dauern, aber was tut man nicht alles für das sichere Löschen der Daten.


Um die Löschmethode effizienter durchzuführen, habe ich die Idee gehabt, einfach einen PXE-Server mit automatischem Löschen aufzusetzen, dieser soll komplett von der restlichen Netzwerk-Infrastruktur getrennt sein, theoretisch wäre es natürlich möglich den PXE-Server auf bestimmte MAC-Adressen einzuschränken, das führt aber in meinem Fall nicht ans Ziel.


Dazu wird im ersten Schritt ein Ubuntu 12.04 Server installiert, es reicht dabei eine minimale Installation, z.B. auf Oracle VirtualBox oder auf kleiner, energieeffizienter Hardware wie z.B. Raspberry Pi.

Nach erfolgter Installation loggt man sich via SSH oder Lokal auf dem System ein und installiert die nötigen Dienste via apt-get.

# paketquellen erneuern
apt-get update

# Installation tftpd-hpa, isc-dhcp-server, syslinux, wget (optional, wenn dban via winscp
# übertragen wird)
apt-get install tftpd-hpa isc-dhcp-server syslinux wget

# stoppen des dhcp-server Dienstes 
service isc-dhcp-server stop

# stoppen des tftpd-hpa Dienstes
service tftpd-hpa stop


Da nun die Dienste soweit installiert sind, geht es an die Konfiguration eben dieser, dazu werden die entsprechenden Konfiguration mit dem Lieblingseditor geöffnet und bearbeitet.

vim /etc/dhcp/dhcpd.conf (absolute minimal Konfiguration):
default-lease-time 600;
max-lease-time 7200;
authoritative;
log-facility local7;
# Achtung: idealerweise wird ein Netz genommen, welches noch nicht vorhanden ist
# Die Netzmaske muss entsprechend der maximal möglichen PXE-Clients gewählt werden,
# bei mir reicht ein /24 vollkommen aus
subnet 192.168.220.0 netmask 255.255.255.0 {
       range 192.168.220.5 192.168.220.240;
       # hier kommt die eigene IP, also des PXE-Servers
       next-server 192.168.220.1;
       # pre boot loader, kommen wir später zu 
       filename "/pxelinux.0";
}

Die Konfiguration des tftpd-hpa ist, Stand Juli 2013, für unsere Zwecke ausreichend und kann dementsprechend im Standard gelassen werden, ggf. kann die Listen-Address (/etc/default/tftpd-hpa) noch auf

Nun beginnen  die Vorbereitungen für das DBAN PXE-Image, dazu wird das Image von www.dban.org via wget oder lokal heruntergeladen und auf den Server, z.B. unter /usr/src, abgelegt und gemountet.

# Download DBAN
wget -O /usr/src/dban-2.2.27.iso "http://downloads.sourceforge.net/project/dban/dban/dban-2.2.7/dban-2.2.7_i586.iso?r=http%3A%2F%2Fwww.dban.org%2Fdownload&ts=1374667513&use_mirror=surfnet"
# Mount der CD
mount -o loop /usr/src/dban-2.2.27.iso /mnt
# Kopieren der dban Dateien in das TFTP Verzeichnis
cp -r /mnt/* /var/lib/tftpboot

Anschließend brauchen wir noch ein Pre-Boot Environment, dazu bedienen wir uns bei syslinux:
# Kopieren 
cp /usr/lib/syslinux/pxelinux.0 /var/lib/tftpboot
# preboot cfg
mkdir /var/lib/tftpboot/pxelinux.cfg
# einfügen der Konfiguration
echo "DEFAULT autonuke"

LABEL autodban
 KERNEL dban.bzi
 APPEND nuke="dwipe --autonuke --method dod522022m" silent
" > /var/lib/tftpboot/pxelinux.cfg/default

Damit haben wir nun alle nötigen Schritte vorbereitet.

Da ich relativ paranoid bin, habe ich das automatische Starten des DHCP-Servers deaktiviert, dadurch wird man gezwungen den DHCP-Server manuell und dadurch bewusst zu starten.
Wichtig: seit Upstart muss man dieses allerdings an zwei Stellen tun!
# deaktiviere init.d dhcp-server beim boot
update-rc -f isc-dhcp-server remove
# deaktivere upstart dhcp-server beim boot
echo "manual" > /etc/init/isc-dhcp-server.override
# starte tftpd-hpa beim boot
update-rc.d tftpd-hpa defaults

Damit der DHCP-Server gestartet werden kann, muss noch die IP-Konfiguration angepasst werden, dazu wird die Datei /etc/network/interfaces mit dem Lieblingseditor geöffnet und eine statische IP eingetragen werden:
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 192.168.220.1
        netmask 255.255.255.0

Nach einem Reboot haben wir nun alle nötigen Arbeiten erledigt und können das automatische Löschen durch Starten des DHCP-Servers beginnen.

Aber Achtung, verbindet das System niemals, mit gestartetem DHCP-Server, mit dem lokalen LAN, denn der neue DHCP-Server könnte schneller als die anderen DHCP-Server antworten und das Telefon würde anschließend wohl nicht mehr still stehen ;-)

Bei Fragen bitte einfach melden!

Montag, 15. Juli 2013

Wie überwache ich Oracle Datenbanken auf Schwellenwerte

Da wir auf Oracle für unsere Applikationen einsetzen, sollen diese natürlich auch überwacht werden.

Das geht relativ einfach mit SQL-Querys, benötigt wird dazu der Oracle Instant Client:
Da ich ein Fan von Automatisierung bin, habe ich für die Installation unter Ubuntu 12.04 (Debian sollte ebenfalls funktionieren, für CentOS/Oracle/Rehadt muss nur das Umwandeln der RPM Pakete auskommentiert werden) ein Skript erstellt.

Das Skript nutzt in der aktuellen Version eine Art Deploymentserver um die RPM Pakete abzuholen, mit geringem Aufwand kann man aber die Aufrufe von wget auch gegen ein cp o.ä. austauschen.
Vor der Nutzung müssen noch die entsprechenden Variablen angepasst werden:
  • Optional: proxy server
  • IP des Deployment Servers
Zusätzlich gibt es noch Abfragen ob der Instant Client mit PHP (interessant für Abfragen mittels PHP-Skripte) oder ohne und in x64 oder x86 installiert werden soll.

Hier das Skript als Download: prepare_ora_inst.sh


#!/bin/bash
# Author: oliver.skibbe (at) gmail.com
# Purpose: install oracle instant client 
# Date: 2013-05-21 15:00

# Variables
SRC=/usr/src
# deployment server, oracle instant client rpms should be there
deploymentserver="deploymentip/dns"
# http / https
http_type="http"
# basic auth
auth="user:password"
# proxy server for pecl/apt
proxy=""

# oracle versions
major=11.2
minor=0.3.0-1
# ora files
files=('basic' 'devel' 'sqlplus')

echo "####################################################"
echo -e "#### Oracle Instant Client Version $major.$minor ####"
echo "####################################################"

echo "Do you want to install 64-bit or 32-bit oracle instant client? 1/2"
read answer

case "$answer" in 
    1)
        arch=x86_64
        shortarch=64
    ;;
    2)
        arch=i386
        shortarch=
    ;;
    *)
        echo "Wrong answer..try again"
        exit 1
esac

echo "Do you want to install php extension? y/N"
read phpanswer
case "$phpanswer" in
        y*|Y*)
        echo "Installing php extension"
        php=true
        ;;
        *)
        echo "Not installing php extension"
        php=false
esac

if [ "x$proxy" = "x" ]; then
    echo "Adding proxy to apt.conf"
    if [ -d /etc/apt/apt.conf.d/ ] ; then
        echo "Acquire::http::Proxy \"$proxy\";" >> /etc/apt/apt.conf.d/99proxy.conf
    else 
        echo "Acquire::http::Proxy \"$proxy\";" >> /etc/apt/apt.conf
fi

# refresh sources
apt-get update

# install rpm to deb converter
apt-get install alien libaio1

for file in ${files[@]}
do
    filename=oracle-instantclient$major-$file-$major.$minor.$arch.rpm
    wget -c --no-proxy -O $SRC/$filename $http_type://$auth@$deploymentserver/deployment/global/oracle/$filename
    if [ $? != 0 ] ; then
        echo "Downloading $filename from $http_type://$deploymentserver/deployment/global/oracle/ failed!"
        exit 1
    fi
    alien -i $SRC/$filename
done

# libs
echo "/usr/lib/oracle/$major/client$shortarch/lib" > /etc/ld.so.conf.d/oracle.conf
ldconfig
ORACLE_HOME=/usr/lib/oracle/$major/client$shortarch

# log directory
mkdir /usr/include/oracle/$major/client$shortarch/log

# includes
ln -s /usr/include/oracle/$major/client$shortarch $ORACLE_HOME/include

# path variables
cat << EOT > /etc/profile.d/oracle.sh 
export ORACLE_HOME=$ORACLE_HOME
export PATH=\$PATH:\$ORACLE_HOME/bin
EOT
chmod +x /etc/profile.d/oracle.sh

if [ $php = true ] ; then
    echo "php oracle stuff"
    export ORACLE_HOME=$ORACLE_HOME
    if [ "x$proxy" = "x" ]; then
        pear config-set http_proxy $proxy
    fi
    pecl install oci8
    echo "extension=oci8.so" > /etc/php5/apache2/conf.d/oci8.ini

    service apache2 reload
fi

exit 0


Nach dem Ausführen des Skripts sollte die Installation des Oracle Instant Clients erfolgreich erledigt sein, anschließend kümmern wir uns nun um den Abfrageteil:

Hier das Beispiel-Skript als Download:  check_oracle_abfrage.php (Username, Password, IP/DNS, DBName und die Abfrage müssen noch angepasst werden!)

#!/usr/bin/php
<?php
// oracle database
$oracleuser = "user";
$oraclepass = "pass";
$instance = "ip/dbname";

$warn = $argv[1];
$crit = $argv[2];

// connect oracle database
$conn = oci_connect($oracleuser,$oraclepass,$instance);

$query = "select count(*) from table where export_date is null";
$abfrage = oci_parse($conn,$query);
oci_execute($abfrage);
$row = oci_fetch_array ($abfrage, OCI_BOTH);

$count = $row[0];

if ( $count >= $crit ) {
        $exitCode = 2;
        $exitString = "CRITICAL";
} else if ( $count >= $warn ) {
        $exitCode = 1;
        $exitString = "WARNING";
} else {
        $exitCode = 0;
        $exitString = "OK";
}

$perfData = "auftraege=$count;$warn;$crit";

// return values
echo $exitString . " {$count} Auftraege|" . $perfData;
exit($exitCode);

?>


Das Skript muss nun noch ausführbar gemacht (chmod +x) oder direkt via php aufgerufen werden.

 Anschließend solltet ihr ein funktionierendes Oracle Abfragesystem haben.


Bei Fragen bitte einfach melden.

Meine entwickelten Nagios Plugins

Da ich sehr viel im Bereich Monitoring, speziell Nagios und Icinga, unterwegs bin, habe ich natürlich mit der Zeit auch einige Plugins selbst geschrieben oder auch andere Plugins nach meinen Vorstellungen, Anforderungen überarbeitet bzw. Funktionen nachgerüstet.

Meine eigenen Plugins sind meist in PHP oder Bash geschrieben, die überarbeiteten Plugins sind in PHP, Perl, Powershell, Python etc. geschrieben.

Die meisten davon gibt es zum freien Download bei http://exchange.nagios.org

Diese Plugins will ich hier kurz vorstellen:

Eigenentwicklungen:
  • check_beronet (geschrieben in PHP)
    Überprüft die hervorragenden VoIP Gateways (ISDN,FXS/FXO,PRI zu SIP) der deutschen Marke Beronet (http://www.beronet.com)
    • Fehler der Ports (inkl. Performance Daten)
    • Status des Links
    • Gesamt Anrufe + durchschnittliche Zeit pro Gespräch (inkl. Performance Daten)
    •  Momentane Anrufe (inkl. Performance Daten)
    • Auslastung der Box/Karte (inkl. Performance Daten)
    • Systeminformationen: Revision, Firmware, Seriennummer
    • Uptime
  • check_all4xxx (geschrieben in PHP)
    Überprüft Sensorboxen [ALL4000/ALL4500] der deutschen Marke Allnet auf Schwellenwerte (z.B. Temperatur, Luftfeuchtigkeit, Rauchsensor, Lichtsensor, ...) und gibt diese inkl. Performance Daten zurück.
    Die Sensoren werden über Zahlenwerte ausgewählt.
  • check_otrs_tickets.php (geschrieben in PHP)
    Eines meiner weiteren Lieblingsprodukte, OTRS das führende Open-Source Ticket-Request System.
    Dieses Plugin überprüft eine konfigurierte OTRS Datenbank auf eine Menge an Tickets. Damit es flexibel ist, können die IDs der entsprechenden States und Queues über einen Parameter abgefragt und konfiguriert werden. Liefert ebenfalls Performance Daten zurück.
  • check_file_count.sh (geschrieben in BASH)
    Überprüft einen Pfad auf eine konfigurierbare Menge an Dateien und gibt die älteste Datei zurück (im Anwendungsfall gibt es einen Spooler, der manchmal nicht aufräumt..), inkl. Performance Daten.
  • check_file_count.ps1 (geschrieben in Powershell)
    Das selbe Plugin, nur für Windows. Überprüft einen Pfad auf eine konfigurierbare Menge an Dateien, inkl. Performance Daten.
Überarbeitete/übernommene Plugins:
  • check_printer (geschrieben in PHP) - hinzugefügt/überarbeitet: Performance Daten, zusätzliche Drucker, snmp v2c, snmp v3 vorbereitet, Bug-Fixes
    Dieses Plugin fragt die gängigsten Drucker mit SNMP 1/v2c und später mit SNMP Version 3 ab
    • counter - z.B. Menge an bisherig gedruckten Seiten, inkl. Performance Daten
    • toner/Ink  - liefert den Füllstand des Toners oder der Tinte zurück, inkl. Performance Daten
    • paper -  liefert den Füllstand der Papiertrays zurück (jedes tray kann separat abgefragt werden!), inkl. Performance Daten
    • hardware - gibt Hardware-Daten (z.B. CPU) zurück
    • parts - gibt den Zustand bestimmter Bauteile zurück
    • alerts - gibt Ereignisse zurück, unwichtige Ereignisse werden soweit ignoriert
    • accounting -  gibt den Status einer vorher konfigurierten Kostenstelle zurück
  • check_ups (geschrieben in PERL) -  hinzugefügt/überarbeitet check_APC: Schwellenwerte, Verbleibende Zeit auf Batterie, Ausgabe, Bug-Fixes
    Dieses Plugin fragt die gängigen USVen ab
    • Globaler Status
    • UPS/USV Type
    • Batteriekapazität
    • Leistung in %
    • Temperatur
    • Leistung in Wh
    • Verbleibende Zeit auf Batterie
    • Seriennummer, Herstellungsdatum (informativ..), Firmware-Version

Das sind natürlich nicht alle, aber schon mal eine gewisse Auswahl, vielleicht hilft es dem einen oder anderen mal.

Falls jemand Interesse, Ideen für neue Plugins oder Änderungen an bestehenden Plugins hat, bitte einfach kurz melden.

Donnerstag, 11. Juli 2013

Wie ändert man den Hostnamen bei einem Ferrari OfficeMaster Gateway

Da unsere Ferrari OfficeMaster Gateways regelmäßig aussteigen und anschließend das Log das Ferrari Messaging Server fluten, habe ich das Logging Ziel auf einen anderen Host umgestellt, der mehr Speicher und Auswertungsmöglichkeiten hat (Logstash + ElasticSearch + Graylog2)

Da die Hostnamen der OMGs aber nicht wirklich eindeutig sind und ich keine bessere Möglichkeit gefunden habe, den Hostnamen anderweitig zu ändern, habe ich mich per SSH mit der Box verbunden und die Skripte durchforstet.

Dabei habe ich folgende Stelle in /etc/init.d/omg.setup gefunden:


# Perhaps hostname defined by user
if [ -e /data/hostname ] ; then
    hn=$(</data/hostname)
    hostname ${hostnamepar} ${hn}
    echo "export HOSTNAME=${hn}" >>/tmp/cmdline
fi


Aus diesem Ausschnitt geht hervor, dass man einfach nur den Hostnamen in /data/hostname ablegen muss, folgender Befehl nimmt einem das ab:

echo "HOSTNAME" > /data/hostname

Anschließend muss die Box neugestartet werden, damit die Änderungen aktiv werden.

Dienstag, 9. Juli 2013

Sekretariate und Chefs die eigene Kalendereinträge erstellen/verändern

Microsoft Outlook bietet nützliche Funktionen um eine Vertretung oder ein Sekretariat zu unterstützen.

Eine wichtige Funktion gibt es allerdings nicht, nämlich eine Information, wenn der Eigentümer eines Kalenders selber Einträge hinzufügt, ändert oder löscht.
Diese Informationen gehen komplett am Sekretariat vorbei und müssten manuell kommuniziert werden, da wir aber die Welt kennen, geht sowas natürlich häufig unter und das Terminchaos ist vorprogrammiert...

Damit das nicht passiert, habe ich eine Outlook VBA Erweiterung geschrieben, welche diese Funktionen nachrüstet.

Achtung, da ich eig. kein VBA-Entwickler bin, kann der Code wahrscheinlich noch besser sein, aber für den Anwendungsfall reicht es wohl..

Unterstützte Funktionen:
  • Hinzufügen eines Eintrags
  • Ändern eines Eintrags
  • Löschen eines Eintrags
  • Separate Anzeige für Ganztägige Termine
  • Übersetzbar
  • Automatisches Ausblenden des Betreff,Inhalt und Ort bei "Privat" Markierung
Beispiel:

E-Mail Subject: Hinzugefügt: Kalendereintrag "Ein neuer Termin" von Skibbe, Oliver

E-Mail Inhalt:
Hinzugefügt: Kalendereintrag "Ein neuer Termin" von Skibbe, Oliver
Start: 10:30 am Mittwoch, 10. Juli 2013
Ende: 13:00 am Mittwoch, 10. Juli 2013
Status: Beschäftigt
Ort: Hannover
Inhalt: Besprechung mit XY zum Thema Z

Und hier der Code:

Option Explicit

'   This program is free software: you can redistribute it and/or modify
'   it under the terms of the GNU General Public License as published by
'   the Free Software Foundation, either version 3 of the License, or
'   (at your option) any later version.
'
'   This program is distributed in the hope that it will be useful,
'   but WITHOUT ANY WARRANTY; without even the implied warranty of
'   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'   GNU General Public License for more details.
'
'   You should have received a copy of the GNU General Public License
'   along with this program.  If not, see <http://www.gnu.org/licenses/.
'
'   Dieses Programm ist Freie Software: Sie können es unter den Bedingungen
'   der GNU General Public License, wie von der Free Software Foundation,
'   Version 3 der Lizenz oder (nach Ihrer Option) jeder späteren
'   veröffentlichten Version, weiterverbreiten und/oder modifizieren.
'
'   Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber
'   OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
'   Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
'   Siehe die GNU General Public License für weitere Details.
'
'   Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
'   Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/
' 
'   Author: Oliver Skibbe ( oliskibbe at gmail.com )
'   Date: 2013-07-09
'   Purpose: Send mail to a configurable person if an calendar event is created, modified or deleted
'   Tested with: Outlook 2007
'   Hints:
'       http://www.vbarchiv.net/commands/details.php?id=formatdatetime
'       http://msdn.microsoft.com/en-us/library/office/bb208262%28v=office.12%29.aspx
'   Changelog:
'       2013-07-01 Oliver Skibbe: first release
'       2013-07-02 Oliver Skibbe: added check if private item (clear text if it is), added busy state to Output
'       2013-07-02 Oliver Skibbe: added allday check


Private WithEvents Items As Outlook.Items
Private WithEvents Reminders As Outlook.Reminders
Private WithEvents objCalFolder As Outlook.folder
Dim objDelFolder As Outlook.folder

' Helper variables
Public isDeletedBool As Boolean
Public reminderOccuredBool As Boolean

' Mail stuff
Public receiverStrg As String
Public subjectStrg As String
Public subjectPartStrg As String
Public bodyStrg As String

' Appointment helper strings
Public apptOrganizerStrg As String
Public apptLocationStrg As String
Public apptStartDateStrg As String
Public apptEndDateStrg As String
Public apptSubjectStrg As String
Public apptBodyStrg As String
Public apptBusyStateStrg As String
Public apptSensitivityStrg As String

' Translation
Public transAllDayEventStrg As String
Public transPrivateItemStrg As String
Public transAtStrg As String
Public transOfStrg As String
Public transAddedStrg As String
Public transChangedStrg As String
Public transDeletedStrg As String
Public transAvailStateStrg As String
Public transTentStateStrg As String
Public transBusyStateStrg As String
Public transOOOStateStrg As String
Public transCalendarStrg As String
Public transBodyStrg As String
Public transStateStrg As String
Public transLocationStrg As String
Public transStartStrg As String
Public transEndStrg As String

' subs
Private Sub Application_Startup()

  ' Receiver
  receiverStrg = "change.me@example.org"
  
  ' translation...modify if needed
  transAllDayEventStrg = "Ganztägig"
  transPrivateItemStrg = "Privater Termin"
  transAtStrg = "am"
  transOfStrg = "von"
  transAddedStrg = "Hinzugefügt"
  transChangedStrg = "Geändert"
  transDeletedStrg = "Gelöscht"
  transAvailStateStrg = "Verfügbar"
  transTentStateStrg = "Mit Vorbehalt"
  transBusyStateStrg = "Beschäftigt"
  transOOOStateStrg = "Abwesend"
  transCalendarStrg = "Kalendereintrag"
  transBodyStrg = "Inhalt"
  transStateStrg = "Status"
  transLocationStrg = "Ort"
  transStartStrg = "Start"
  transEndStrg = "Ende"
  
  Dim Ns As Outlook.NameSpace
  Dim CalFolder As Outlook.MAPIFolder
  Set Reminders = Outlook.Application.Reminders
  
  ' default values ..
  isDeletedBool = False
  reminderOccuredBool = False
    
  Set Ns = Application.GetNamespace("MAPI")
  Set CalFolder = Ns.GetDefaultFolder(olFolderCalendar)
  ' Set Folder = CalFolder.Folders("Subfolder")
  Set Items = CalFolder.Items
  
  Set objCalFolder = Application.Session.GetDefaultFolder(olFolderCalendar)
  Set objDelFolder = Application.Session.GetDefaultFolder(olFolderDeletedItems)
  
End Sub

' item add event
Private Sub Items_ItemAdd(ByVal Item As Object)
    ' Objects
    Dim Appt As Outlook.AppointmentItem
    
    ' if meeting or appointment..
    If TypeOf Item Is Outlook.AppointmentItem Or TypeOf Item Is Outlook.MeetingItem Then
        ' Appointment properties
        Set Appt = Item
        ' mail strings
        apptOrganizerStrg = Appt.Organizer
        ' AllDayEvent
        If Appt.AllDayEvent = True Then
            apptStartDateStrg = transAllDayEventStrg + ", " + FormatDateTime(Appt.Start, 1)
            ' we have to subtract one day to get a nice looking result
            apptEndDateStrg = transAllDayEventStrg + ", " + FormatDateTime(DateAdd("d", -1, Appt.End), 1)
        Else
            apptStartDateStrg = FormatDateTime(Appt.Start, 4) + " " + transAtStrg + " " + FormatDateTime(Appt.Start, 1)
            apptEndDateStrg = FormatDateTime(Appt.End, 4) + " " + transAtStrg + " " + FormatDateTime(Appt.End, 1)
        End If
        apptLocationStrg = Appt.Location
        apptBusyStateStrg = format(Appt.BusyStatus)
        apptSensitivityStrg = format(Appt.Sensitivity)
        apptBodyStrg = Appt.Body
        apptSubjectStrg = Appt.Subject
        subjectPartStrg = transAddedStrg
        
        ' send mail
        Call sendMail
    End If
End Sub

' busy state mapping
'   olBusy          2   The user is busy.
'   olFree          0   The user is available.
'   olOutOfOffice   3   The user is out of office.
'   olTentative     1   The user has a tentative appointment scheduled.
Private Sub busyState()
    Select Case apptBusyStateStrg
        Case Is = 0
            apptBusyStateStrg = transAvailStateStrg
        Case Is = 1
            apptBusyStateStrg = transTentStateStrg
        Case Is = 2
            apptBusyStateStrg = transBusyStateStrg
        Case Is = 3
            apptBusyStateStrg = transOOOStateStrg
        End Select
End Sub

' check if an calendar item is about to be deleted
Private Sub objCalFolder_BeforeItemMove(ByVal Item As Object, ByVal MoveTo As MAPIFolder, Cancel As Boolean)

    ' Objects
    Dim Appt As Outlook.AppointmentItem

    ' if meeting or appointment..
    If TypeOf Item Is Outlook.AppointmentItem Or TypeOf Item Is Outlook.MeetingItem Then
        ' Appointment properties
        Set Appt = Item
        
        ' saving calendar values for later purposes
        apptOrganizerStrg = Appt.Organizer
        ' AllDayEvent
        If Appt.AllDayEvent = True Then
            apptStartDateStrg = transAllDayEventStrg + ", " + FormatDateTime(Appt.Start, 1)
            ' we have to subtract one day to get a nice looking result
            apptEndDateStrg = transAllDayEventStrg + ", " + FormatDateTime(DateAdd("d", -1, Appt.End), 1)
        Else
            apptStartDateStrg = FormatDateTime(Appt.Start, 4) + " " + transAtStrg + " " + FormatDateTime(Appt.Start, 1)
            apptEndDateStrg = FormatDateTime(Appt.End, 4) + " " + transAtStrg + " " + FormatDateTime(Appt.End, 1)
        End If
        apptLocationStrg = Appt.Location
        apptBusyStateStrg = format(Appt.BusyStatus)
        apptSensitivityStrg = format(Appt.Sensitivity)
        apptBodyStrg = Appt.Body
        apptSubjectStrg = Appt.Subject
    End If
    
    ' check if item is deleted
    If MoveTo Is Nothing Then
        isDeletedBool = True
        Call Items_ItemChange(Item)
    ElseIf MoveTo = objDelFolder Then
        isDeletedBool = True
        Call Items_ItemChange(Item)
    End If
End Sub

Private Sub Items_ItemChange(ByVal Item As Object)
   
    ' Objects
    Dim Appt As Outlook.AppointmentItem
    
    ' if meeting or appointment..
    If TypeOf Item Is Outlook.AppointmentItem Or TypeOf Item Is Outlook.MeetingItem Then
    
        ' leave sub if just a reminder occured ( is set by: Sub Reminders_BeforeReminderShow )
        If reminderOccuredBool = True Then
            Exit Sub
        End If

        Set Appt = Item
        
        ' is set by Sub objCalFolder_BeforeItemMove
        If isDeletedBool = False Then
            apptOrganizerStrg = Appt.Organizer
            ' AllDayEvent
            If Appt.AllDayEvent = True Then
                apptStartDateStrg = transAllDayEventStrg + ", " + FormatDateTime(Appt.Start, 1)
                ' we have to subtract one day to get a nice looking result
                apptEndDateStrg = transAllDayEventStrg + ", " + FormatDateTime(DateAdd("d", -1, Appt.End), 1)
            Else
                apptStartDateStrg = FormatDateTime(Appt.Start, 4) + " " + transAtStrg + " " + FormatDateTime(Appt.Start, 1)
                apptEndDateStrg = FormatDateTime(Appt.End, 4) + " " + transAtStrg + " " + FormatDateTime(Appt.End, 1)
            End If
            apptLocationStrg = Appt.Location
            apptSensitivityStrg = format(Appt.Sensitivity)
            apptBusyStateStrg = format(Appt.BusyStatus)
            apptBodyStrg = Appt.Body
            apptSubjectStrg = Appt.Subject
            subjectPartStrg = transChangedStrg
        Else
            ' part of subject
            subjectPartStrg = transDeletedStrg
        End If
  
        ' send mail
        Call sendMail
    End If
End Sub

' Before reminder is shown, set variable to surpress ItemChange
Private Sub Reminders_BeforeReminderShow(Cancel As Boolean)
    reminderOccuredBool = True
End Sub

' finally prepare and send mail
'       olConfidential  3   Confidential
'       olNormal        0   Normal sensitivity
'       olPersonal      1   Personal
'       olPrivate       2   Private
Private Sub sendMail()
    
    Dim OutApp As Object
    Dim OutMail As Object

    Set OutApp = CreateObject("Outlook.Application")
    Set OutMail = OutApp.CreateItem(0)
    
    ' default values for body and location string
    If LenB(apptBodyStrg) = 0 Or apptSensitivityStrg > 0 Then
        apptBodyStrg = "-"
    End If
    If LenB(apptLocationStrg) = 0 Or apptSensitivityStrg > 0 Then
        apptLocationStrg = "-"
    End If
    If LenB(apptSubjectStrg) = 0 Or apptSensitivityStrg > 0 Then
        apptSubjectStrg = "-"
    End If
    If apptSensitivityStrg > 0 Then
        subjectPartStrg = transPrivateItemStrg + ": " + subjectPartStrg
    End If
    
    ' busy State string mapping
    Call busyState
    
    ' Subject
    subjectStrg = subjectPartStrg + ": " + transCalendarStrg + " " + Chr(34) + apptSubjectStrg + Chr(34) + " " + transOfStrg + " " + apptOrganizerStrg
    ' Body
    bodyStrg = subjectPartStrg + ": " + transCalendarStrg + " " + Chr(34) + apptSubjectStrg + Chr(34) + " " + transOfStrg + " " + apptOrganizerStrg + vbCrLf _
                    + vbCrLf _
                    + transStartStrg + ": " + apptStartDateStrg + vbCrLf _
                    + transEndStrg + ": " + apptEndDateStrg + vbCrLf _
                    + transStateStrg + ": " + apptBusyStateStrg + vbCrLf _
                    + transLocationStrg + ": " + apptLocationStrg + vbCrLf _
                    + vbCrLf _
                    + transBodyStrg + ": " + apptBodyStrg
                    
    With OutMail
        .To = receiverStrg
        .Subject = subjectStrg
        .Body = bodyStrg
        .Display
        .Send
    End With
End Sub

Der Code muss in den Visual-Basic Editor (Alt+F11) unter: Projekt1 -> Microsoft Office Outlook -> ThisOutlookSession beim Kalendereigentümer eingebunden werden.

Ich hoffe es hilft einigen Leuten, gerade der Part mit gelöschten Mails war etwas knifflig.



Bei Fragen zu dem Code kontaktiert mich bitte