Montag, 28. Oktober 2013

Windows Print Server und die Druckerwarteschlange

Ein typisches Problem in der Windows Printserver Welt scheinen fehlerhafte (=hängende) Druckeraufträge zu sein.
Wenn ein Druckauftrag fehlerhaft ist, blockiert er diesen Drucker, bis dieser Druckauftrag entfernt wurde.

Damit blockierte Drucker nicht durch die BenutzerInnen gemeldet werden müssen (immer unschön und wenig professionell), habe ich einen Check in VBSkript für Nagios/NSclient geschrieben, welcher alle registrierten Drucker mittels WMI auf eben diese Aufträge prüft:

' MSDN: http://msdn.microsoft.com/en-us/library/aa394370%28v=vs.85%29.aspx
' Required Variables
' Author: Oliver Skibbe oliskibbe (at) gmail.com
' Date: 2013-10-30
Const PROGNAME = "check_print_spooler"
Const VERSION = "1.1.0"

' Nagios helper functions
Include "C:\Programme\NSClient++\scripts\lib\NagiosPlugins.vbs"

' Arguments
strComputer = WScript.Arguments.Item(0)

' Defaults
return_code = 0
return_msg = "Everythings fine"

' automatically kill job?
killjob = true


' Create WMI object
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

' Create the NagiosPlugin object
Set np = New NagiosPlugin

' Fetch all jobs with status error
Set Result = objWMIService.ExecQuery("Select * From Win32_PrintJob Where Status = 'Error'")

For Each instance In Result

 ' automatic job kill
 If killjob = True Then
  instance.Delete_
 Else
  ' JobId is attached to "caption, description and name" thus we want to split and accessible with printerName(1)
  printerName = Split(instance.Caption,",")
  ' if job should not be automatically killed, print critical and printer name
  failedPrinterStr = failedPrinterStr & " " & Chr(34) & printerName(0) & Chr(34)
  return_code = 2
 End If
next

If return_code > 0 Then
 return_msg = "Job Errors on printer " & failedPrinterStr
End If

' Nice Exit with msg and exitcode
np.nagios_exit return_msg, return_code


Sub Include( cNameScript )
    Set oFS = CreateObject("Scripting.FileSystemObject")  
    Set oFile = oFS.OpenTextFile( cNameScript )
    ExecuteGlobal oFile.ReadAll()
    oFile.Close
End Sub

Dieses Skript sollte über einen Windows-Server z.B. über NRPE (mit NSCLIENT++) aufgerufen werden und nimmt als Argument den Computernamen an.

Dazu nimmt man folgende Einstellungen im NSClient vor (gilt für Version 0.38):
; Script to check external scripts and/or internal aliases.
CheckExternalScripts.dll
 
[NRPE]
;# NRPE PORT NUMBER
;  This is the port the NRPEListener.dll will listen to.
port=5666

;# COMMAND TIMEOUT
;  This specifies the maximum number of seconds that the NRPE daemon will allow plug-ins to finish executing before killing them off.
command_timeout=60

;# COMMAND ARGUMENT PROCESSING
;  This option determines whether or not the NRPE daemon will allow clients to specify arguments to commands that are executed.
allow_arguments=1

[External Scripts]
check_print_spooler=cscript.exe //T:30 //NoLogo scripts\check_print_spooler.vbs $ARG1$

Der aufmerksame Leser wird festgestellt haben, dass dem Spooler ein Argument übergeben werden kann, dieses nimmt den Namen des Servers an, das heißt man kann von einem Server aus viele andere Server angesprochen werden können, dazu muss dann allerdings der NSClient++ Dienst mit einem entsprechenden User gestartet werden, für den rein lokalen Aufruf ("." oder "localhost") reicht das Systemkonto.

Im Nagios muss dann folgendes in die commands.cfg eingetragen werden:

Entweder die Verteilung des Service über einen Host oder eine Hostgruppe (Argument wäre der "Hop"-Server):
define command {
        command_name    check_windows_print_spooler
        command_line    $USER1$/check_nrpe -H $ARG1$ -c check_print_spooler -a $HOSTADDRESS$
}

Oder als rein lokale Variante:
define command {
        command_name    check_windows_print_spooler
        command_line    $USER1$/check_nrpe -H $HOSTADDRESS$ -c check_print_spooler -a "."
}


Folgende Dinge werde ich wohl noch implementieren:
  • Flag für automatisches Löschen der Jobs
  • E-Mail an Druckjobersteller, dass der Job abgebrochen wurde
  • Umbau auf PHP mit Nutzung von "wmic", d.h. Hop-Server wird überflüssig 

* UPDATE:  Damit das Skript besser skaliert, habe ich den Code etwas umgebaut und ein Flag eingebaut, welches den fehlerhaften Job automatisch löscht.

Download: https://dl.dropboxusercontent.com/u/9482545/check_print_spooler.vbs

Nachtrag zum automatischen Löschen via DBAN

Nachdem nun endlich der Löschserver zum (sehr erfolgreichen) produktiven Einsatz gekommen ist, habe ich doch einen kleinen Fallstrick entdeckt..

Was passiert wenn ich viele Geräte auf einmal einschalte? Richtig, die Sicherung kann sich verabschieden, da beim Starten der PCs erhöhte Last auf der Stromleitung stattfindet...

Fazit: entweder die PCs auf verschiedene Stromkreise aufteilen oder Schritt für Schritt die PCs starten



Backlink: http://oskibbe.blogspot.de/2013/07/wie-loscht-man-sicher-effizient-und.html

 

Passive Sicherheit im AD mit PHP

Ab und an kommt es mal vor, dass ein User im AD erhöhte Rechte oder zusätzliche Gruppen bekommen muss.
Zum Beispiel könnte dieser erhöhte Rechte im Internet (AD-basierte Proxygruppen) oder für eine Applikation (Argh!) benötigen.

Was passiert aber nun, wenn diese Rechte in Vergessenheit geraten und dadurch Schaden (z.B. Data-Loss, Malware, ...) entsteht? Die Frage ist natürlich reichlich polemisch, allerdings muss man sich die Frage stellen, wer dafür die Verantwortung trägt? Natürlich die IT.

Und genau die kann dafür sorgen, dass solche Dinge NICHT in Vergessenheit geraten, indem man sich automatisch in periodischen Abständen ein Aufstellung der wichtigsten Gruppen zuschicken lässt.

Genau dieses erfüllt das Skript, welches ich, aus den oben erwähnten Gefahren heraus, geschrieben habe:


#!/usr/bin/php -q
<?php
/*
 Read dom admins from AD
 Author: Oliver Skibbe oliskibbe (at) gmail.com
 Date: 2013-10-28
*/

function parseLdapDn($dn)
{
        $dn = addcslashes( $dn, "<>" );
        $result = ldap_explode_dn( $dn, 0 );
        //translate hex code into ascii again
        foreach( $result as $key => $value )
                $result[$key] = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $value);
        unset($result["count"]);
        return $result;
}

function sendMail ( $bodyStuff ) {

        global $debug, $dryrun, $to, $replyto, $cc, $from;

        // mail header
        $header = "MIME-Version: 1.0\r\n";
        $header .= "Content-type: text/html; charset=utf-8\r\n";
        $header .= "From: " . $from . "\r\n";
        $header .= "Reply-To: " . $replyto . "\r\n";
  if ( $cc ) {
   $header .= "Cc: " . $cc . "\r\n";
  }
        $header .= "X-Mailer: PHP ". phpversion();
  
        $subject = "Mitglieder der Gruppe {$bodyStuff["name"]}";
        // erfüllt seinen Zweck..
  $body = "<html>
<br>
Mitglieder der Gruppe {$bodyStuff["name"]}
<br><br>
<table border=\"1\">
<tr><th align=\"left\"><b>Username</b></th><th align=\"left\"><b>OU</b></th></tr>\n\n";

        unset($bodyStuff["name"]);

        foreach ( $bodyStuff as $members) {
                foreach ( $members as $member => $foo ) {
                        $body .= "<tr><td nowrap>{$member}</td><td>{$foo}</td></tr>\n";
                }
        }
        $body .= "\n</table>
</html>";
        if ( $debug  || $dryrun ) {
                echo "Subject " . $subject . PHP_EOL;
                echo "Body: " . $body . PHP_EOL;
                echo "Header: " . $header . PHP_EOL;
        }
        if ( ! $dryrun ) {
                $result = mail( $to, $subject, $body, $header);
        } else {
                $result = true;
        }

        return $result;
}

// Variables

// debug prints
$debug = false;

// do not send mail, just print
$dryrun = false;

// receiver
$to = "oliskibbe@gmail.com";
$replyto = "ticketsystem@example.tld";
$from = "absender@example.tld";

// optional
$cc = "chef@example.tld";

// ad
$host = "ADHOST";
$user = "ADUSER";
$pass = "ADPASS";

// erlaube mehrere Gruppen z.B. Domänen-Admin und Proxy Gruppen ohne Beschränkung
$filter = "(&(|(name=Domänen-Admins)(name=FreiesInternet))(objectcategory=group))";
$dn = "dc=example,dc=local";
// nur Name und Mitglieder
$justthese = array('name', 'member');

// mit AD verbinden
$link = ldap_connect($host) or die ("Keine Verbindung zu {$host} möglich!" . PHP_EOL);

// AD Protokoll Einstellungen setzen
ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($link, LDAP_OPT_REFERRALS, 0);

// Am AD anmelden
$bind = ldap_bind($link, $user, $pass) or die ("Keine Anmeldung am AD mit User {$user} an Host {$host} möglich!");

$searchresult = ldap_search ($link, $dn, $filter, $justthese );

$result = ldap_get_entries($link, $searchresult);

ldap_close($link);

// helper

foreach ( $result as $groupArray ) {
        $members = array();

        if (is_array ($groupArray) ) {

                $members["name"] = $groupArray["name"][0];

                // remove count stuff
                unset($groupArray["member"]["count"]);

                foreach ( $groupArray["member"] as $member ) {
                        $dnResult = parseLdapDn($member);

                        $name = str_replace("CN=","",$dnResult[0]);
                        unset($dnResult[0]);

                        $members[$members["name"]][$name] = implode(",", $dnResult);

                        unset($member);
                        unset($dnResult);
                }
                $result = sendMail($members);
                if ( ! $result && $debug ) {
                        echo "Sending mail failed!";
                }
        }
}

Dieses Skript fragt 1-n Gruppen über den LDAP Filter ab und verschickt wunderschöne E-Mails  mit einer tabellarischen Aufstellung der entsprechenden User und in welcher OU sich diese befinden.

Download

Anschließend trägt man dieses Skript in die crontab ein:
0 01 * * 1     /usr/bin/php -q /usr/local/bin/check_ad_admins.php

Nun wird man jede Woche am Montag zum Arbeitsbeginn eine E-Mail mit den aktuellen Gruppenmitgliedern haben, bei sehr großen Firmen sollte man vielleicht noch einen Zwischenschritt einlegen und entweder eine kleine Datenbank oder ein Textfile mit Altwerten füllen um die neu hinzugekommen zu highlighten, aber in meinem Fall war das nicht nötig.


Bei Fragen wie immer einfach melden.