Win:Updates

Aus KissDoc

Wechseln zu: Navigation, Suche

Inhaltsverzeichnis

Top

Allgemein

Wir sind eigentlich keine Versionshascher (von 2.2.3 auf 2.2.4 oä.), es sei denn ein Update wird wirklich benötigt. Dazu sollte man sich zunächst mal die ChangeList durchlesen. Da wir hier aber von kommerzieller Software sprechen, ist ein Win-Update allein schon aus Sicherheitsgründen unabdingbar. Es ist schon fast ein Updatewahn. Unter Debian gibts die Probleme nicht, da Software erst dann als stabil eingestuft wird, wenn keine bekannten Fehler mehr existieren. Kein kommerzieller Anbieter veröffentlicht noch nicht einmal seine bekannten Fehler ;). Wie auch immer, fehlerfreie Software gibt es sowieso nicht.

Um nun allen öffentlichen Workarounds für Komprimittierungen unter Win entgegen wirken zu können, muß man wohl oder übel diesen Updatewahn mitmachen. Dabei werden meist mehrere bekannte Fehler behoben und einige neue unbekannte eingespielt. Für ein WindowsUpdate (WU) gibt es drei Wege: das (normale) Windows Update, SUS (Software Update Services) und Systems Management Server (SMS) mit SUS Feature Pack. Die ersten zwei Möglichkeiten stellt Microsoft frei zur Verfügung. Der SUS und SMS setzt aber leider einen MS Server voraus und entfällt somit im eigenen Netz. SUS, wie auch Windows Update (WU) unterstützen lediglich das Betriebssystem an sich und Betriebsystemkomponenten wie Microsoft Internet Explorer, Microsoft Internet Information Services and Microsoft Windows Media® player. Andere Microsoft Produkte lassen sich nicht mit SUS aktualsieren. Eine Lösung auf Basis von SMS kann auch andere Anwendungen aktualisieren. Tools wie der MBSA funktionieren ab NT5.0 und dienen zur "Sicherheits"-Analyse des Systems.

MS verwendet Namen wie "SecurityPatchManagement", "Palladium", "NGSCB" (Next-Generation Secure Computing Base), "Unity" und "Security Startup" und hat weiterhin einige Softwareprodukte zur Problematik erschaffen. Witzig ist die Versionsnummer vom "Microsoft Guide to Security Patch Management v1" welches unter der URL http://go.microsoft.com/fwlink/?LinkId=16286 frei zum Download stand. Da dies nicht mehr zur Verfügung steht, wird deutlich, dass das Thema Sicherheit bei MS ein schwarzes Kapitel ist und sich die Konzeption und deren Namen aller 14 ändern (ct 11/2005 WinLonghorn S.114). Wir haben mal die ehemalige PDF Datei der Bild:MSGuide SPM.pdf zur nachträglichen Einsicht bereitgestellt. Denkbar ist natürlich auch, das MS mit einem sicheren Windows Geld verdienen will. Somit würden Begriffe wie Patch, Update und Upgrade durch den Kommerz mal wieder verwaschen.

Top

ServicePacks

Bei den Servicepacks (SP) von MS handelt es sich primär um Bugfixings und einer Zusammenfassung der Security Patches. Aber auch Neuerungen, welche MS sich beim Anwender wünscht, werden dabei eingespielt. Im allgemeinen ist es aber eine bekannte und bewährte Technik unter den NT Systemen.

ServicePacks in die Grundinstallation zu integrieren bietet dabei die Möglichkeit, sich bei einer Neuinstallation das nachträgliche "Update" bzw. Bugfixing zu ersparen. Zunächst muß das SP zunächst entpackt werden. Dies kann mit einem normalen Packer á la WinRar oder auf der Konsole mit sp*.exe -x realisiert werden. Der Zielordner wird dabei vom User abgefragt. In diesem wird dann ein Verzeichnis namens i386 erstellt, welches das eigentliche SP enthält.

Für die Integration bietet Microsoft ein Tool namens update.exe an, welches im SP-Unterordner update zu finden ist. Mit dem Parameter /s: wird der Pfad zu den Windows-Installationsdateien angegeben. Wird dieser Parameter nicht angegeben, so bezieht sich das Update auf das aktuelle System. ACHTUNG: der Pfad des Parameters bezieht sich auf den Ordner, welcher wiederum zwingend den Ordner i386 enthält. Existiert i386 nicht oder ist er anders benannt, kommt es einer Fehlermeldung. Im folgendem Beispiel befinden sich die Installationsdaten in x:\win2k\i386:

X:\sp4\i386\Update\Update /S:X:\Win2k

Der erfolgreiche Abschluss lässt sich mit einem Blick in die Datei Svcpack.log überprüfen.

Top

Security Patches

Die Installation des Win-Rechners mit dem aktuellen Servicepack ist noch lange keine Garantie, daß unser System auf dem neuesten Stand ist. Spätestens nach 14 Tagen gibt es wahrscheinlich einen neuen Patch, der Bugs behebt (und vielleicht neue mit sich bringt :-)). Aber die Jungs von MS kümmern sich darum - jeden zweiten Mittwoch ist wohl Patch-Tag. Wozu welcher Patch dient wird auch meist gut beschrieben. Die bereitgestellten Patches liegen bei MS als selbstextrahierende .exe zum freier Download bereit, wobei die Patchbezeichnungen seit Windows 2000 vereinheitlicht sind. Damit sind diese aber noch lange nicht auf unserem PC. Es gibt zwar das automatische MS-Update, aber jeder Client müsste sich das alles selbst holen. Wir verfolgen die Lösung eines zentralen Downloads auf einem Server - und die Clients holen sich per at-Job regelmässig die neuen Patches vom lokalen Netz. Dabei wird auf einen SUS-Server (Software Update Service) verzichtet, unser Server ist nun mal eine Linuxkiste.

Top

Patches installieren

Die Installation eines Patches wird von vielen Usern als einfach beschrieben - mit einem Doppelclick auf die *.exe wird man durch ein Menue geführt und fertig. Für uns als Admins soll das ganze aber möglichst automatisiert ablaufen. Dazu existieren Optionen (Installationschalter) für eine silent-Installation, zum Unterdrücken eines automatischen Reboot, usw.. Und da beginnen unsere Probleme.

Microsoft hat im Laufe der Jahre verschiedene Installationsmechanismen eingesetzt, die auch verschiedene Optionen zur Folge haben. Seit Ende 2004 sollen nur noch einheitliche Befehlszeilenoptionen für neue Patches verwendet werden, die auf einer im Patch enthaltenen update.exe >=5.3.24.3 basieren. Lassen wir uns mal überraschen. Zumindest die Patchliste (XML) ist Mitte 2005 schon wieder durch ein neues Konzept ersetzt worden und steht nicht mehr aktuell zur Verfügung :(

Ältere Patches funktionieren mit einer update.exe < 5.3.24.3, hotfix.exe, dahotfix.exe oder anderen. Die verschiedenen Optionen der Patches liegen teilweise sogar so ungünstig, dass eine Option eines Installers eine völlig andere Bedeutung bei einem anderen Installer hat. Da momentan noch eine ganze Menge Patches der älteren Generation relevant sind, ist eine Automatisierung ohne zusätzliche manuelle Arbeit schier unmöglich. Hinzu kommt noch, dass die meisten Patches keine Überprüfung vornehmen, ob sie nicht schon installiert sind.

Top

Patch-Liste

Microsoft stellt ein Liste zur Verfügung, welche Patches existieren. Dabei handelt es sich beim Download um eine gepackte mssecure_1031.cab, die eine mssecure_1031.xml (ca. 2 MB) enthält. Die Nummer 1031 besagt die deutschsprachige Variante. Hier sind alle Informationen zu Patches über MS-Produkte enthalten. Weitere Infos zum Download der XML Datei gibts bei MS. Die Struktur des Files ist auch auf Grund der Datenmenge auf den ersten Blick nicht ganz einfach zu überblicken. Unter dem Root-Node BulletinDatastore sind folgende Knoten für uns interessant:

  • Bulletins - die verfügbaren Bulletins ("schwarze Bretter") mit den Patches für die entsprechenden Produkte und deren Servicepacks
  • Products - eine Liste der MS-Produkte</li>
  • Locations - Die Download-Adressen der Patches

Wir müssen zunächst ermitteln, welche Produkte mit welchem Servicepack für uns überhaupt in Frage kommen. Den Namen des Produktes finden wir in /BulletinDatastore/Products/Product/@Name und merken uns zwei weitere Attribute des Knotens: @ProductID und @CurrentServicePackID. Für unsere eigentlichen Patches wichtig ist die Patch-ID, die wir in /BulletinDatastore/Bulletins/Bulletin/@BulletinID finden. Es handelt sich dabei aber nicht um eine Zahl, sondern eine Bezeichnung, z.B. MS04-040. Dieser BulletinID liefert uns auch der MBSA. In diesem Knoten sind dann die Attribute wie Beschreibung, Datum u.ä. zu finden. Pro Bulletin existieren ein oder mehrere Sub-Node(s) Patches/Patch, weil ein Bulletin ja für verschiedene Produkte/Servicepacks existieren kann. In Patch/@PatchLocationID steckt die ID, mit der wir für die Download-Location die richtige Download-Adresse herausbekommen. Wir müssen aber vorher noch ermitteln, ob der Patch auch zu unserem Produkt/Servicepack passt. Dafür prüfen wir in den weiteren Sub-Nodes die Attribute AffectedProduct/@PruductID bzw. AffectedProduct/AffectedServicePack/@ServicePackID, die beide übereinstimmen müssen. Haben wir nun unseren richtigen Patch gefunden, dann nehmen wir dessen Download-Location aus Patch/@PatchLocation und suchen uns damit in /BulletinDatastore/Locations/Location/@LocationID die richtige Downloadadresse.

Leider existiert zu jedem Patch nicht wirklich ein echtes Binary zum Patchen. Als Uri erhält man bei einigen Downloadlinks Angaben wie NA (not available), der Link ist eine Übersichtswebseite zum "Lesen" oder einfach ein Leerstring. Keine Ahnung was das soll. Wenn man dann auf der Webseite rumklickert findet man dann doch irgendwann den entsprechenden Download. Es scheint fasst so, als ob einige Produkte wie W2K von MS langsam aufgegeben werden. Damit sind diese "Patches" für uns ein Grauen, zumindest was eine Automatisierung in Scripten betrifft.

Top

MBSA

MBSA (Microsoft Baseline Security Analyzer) ist ein Freewaretool von Microsoft, welches mit MBSASetup-DE.msi /q auch silent wunderbar installiert werden kann. Mit dem Tool wird der Windows-Rechner bezüglich der Security-Updates/Servicepacks analysiert. Dazu existiert ein GUI mbsa.exe und ein Konsolentool mbsacli.exe, bei MS auch ganz gut dokumentiert. Wir verwenden für unsere Zwecke nur die mbsacli.exe. Zur Analyse benötigt der MBSA eine Datei mssecure_1031.cab bzw. mssecure_1031.xml. Falls beim Aufruf des MBSA kein cab/xml-File angegeben wird, lädt sich das Programm das File selbst von MS per http herunter. Der Pfad zu Microsoft ist scheinbar fest im Binary eingebrannt, zumindest lässt sich in der Registry nichts dazu finden. Damit mbsacli.exe auch arbeitet benötigen wir noch [Win:MSXML|MSXML 3.0] oder höher. Für remote-Überprüfungen sind noch einige Dienste Clientseitig notwendig, was uns aber hier nicht interessiert :-)

Mit mbsacli.exe -hf -nvc -x mssecure_1031.xml wird die lokale Maschine analysiert und ein Liste über den Rechner betreffenden Patches auf der Konsole ausgegeben. Warungen und Hinweise werden auch mit aufgelistet. Einige davon sind echt schräg, warum soll ich z.Bsp. einen Patch für DirectX 7 installieren, wenn DirectX 9 installiert ist. Die Option -hf muss zuerst angegeben werden, damit mbsacli.exe im hfnetchk-Modus arbeitet (erweiterte Analysemöglichkeiten). hfnetchk wurde von Shavlik Technologies für Microsoft entwickelt, die das xml-File auch zum Download anbieten. Eine Hilfe mit mbsacli.exe /? ist nur verfügbar, wenn MBSA installiert wurde. Die Hilfe für den hfnetchk-Modus ist allerdings standalone mit mbsacli.exe -hf -? aufrufbar. Bei einem Einsatz in Scripten ist eine stdout-Parsung natürlich recht aufwendig. Mit mbsacli.exe -hf -sms -x mssecure_1031.xml -f out.xml -unicode -nvc -history 2 -s 2 wird uns jetzt ein schönes xml-File erzeugt, was nur noch die für uns wichtigen Informationen enthält.

Der Exitcode von mbsacli.exe teilweise auch bei fehlerhaften Aufrufen 0, z.Bsp. wenn ein angegebenes xml-File nicht existiert. Nicht gerade fein ...

Top

Scripte zur Automatisierung

Wir brauchen eigentlich zwei Scripte: bei dem einen lädt unser Server neue Patches aus dem Netz herunter, um diese für die Clients im lokalen Netz bereitstellen zu können. Beim zweiten Script muss der MS-Client sein System mit mbsacli.exe überprüfen und ggf. die neuen Patches einspielen. Hier haben wir aber zunächst ein kleines Problem: woher weiss der Server, welche Patches die Clients brauchen? Es handelt sich ja um einen Linuxrechner, welcher die Clients schlecht abfragen kann. Der Server kann also nur aufgrund einer manuell modifizierten Liste alle in Frage kommenden Patches herunterladen. Damit kann der Client aber unter Umständen bei neu installierten Produkten/Servicepacks nicht alle Patches installieren. Lösung: Wir machen aus der Not eine Tugend. Wie kommt der Client dazu, eine Software zu fahren, die dem Server nicht bekannt ist ?!? Wir reden ja in unserem Netz von absolut gleichen Arbeitsstationen, zumindest was die Software betrifft. Eine Log-Fehler-Meldung des Client ist also eine sehr gute Information an den Administrator, daß auf dem Win-Rechner etwas läuft, was gar nicht laufen sollte. Einige weitere Scriptbeispiele findet ihr auch bei MS.

Top

Server-Script

Auf dem Server verwenden wir ein PHP CLI Script, welches zyklisch per Cron-Job angestossen wird. Es lädt sich die aktuelle Patchliste mssecure_1031.cab herunter, entpackt es zur xml-Datei, parst das File aufgrund der eingetragenen Produkte/Servicpacks und lädt die entsprechenden Patches aus dem Netz. Der meist original kryptische Dateiname eines Patches wird dabei nach folgendem Prinzip gesetzt: PRODUCT_SP_BULLETIN.EXE.

Es existieren aber noch die beiden Umstände, dass teilweise Uri's zu Patches in der mssecure_1031.cab nicht existieren, und der Client später nicht weiss, mit welchen Optionen (Installationschalter) der Patch zu installieren ist. Darum schreibt das Serverscript seine ermittelbaren Patchinformationen in eine xml-Datei msclient.xml. Nicht ermittelbare Informationen müssen per Hand eingetragen werden. Die Datei msclient.xml hat einen einfachen Aufbau mit folgender Struktur (Beispieldaten):

<xml version="1.0">
<patches>
	<patch binary="WINDOWS 2000 PROFESSIONAL SP4"  bulletin="MS02-050" ignore="0" arguments="/q /n" uri="http://..."/>
	<patch binary="WINDOWS 2000 PROFESSIONAL SP4"  bulletin="MS03-023" ignore="0" arguments="/q /n" uri="http://..."/>
	<patch binary="INTERNET EXPLORER 6 SP1"        bulletin="MS03-040" ignore="0" arguments="/q /n" uri="http://..."/>
	<patch binary="INTERNET EXPLORER 6 SP1"        bulletin="MS03-048" ignore="0" arguments="/q /n" uri="http://..."/>
</patches>

Das xml-File wird vom Serverscript bei neuen Informationen aktualisiert, damit unsere händigen Informationen nicht verloren gehen. Mit jedem neuen Patch müssen also vom Admin mindestens die Installationsparameter nachgetragen werden. Erst jetzt sind alle Informationen für den Client zur automatischen Installation verfügbar. Wenn laut mssecure.xml ein Patch existiert, aber kein Patch-File verfügbar ist (oder keinen Sinn macht), kann mit dem Attribut ignore der Patch für den Client auch deaktiviert werden.

Zur Vollständigkeit wollen wir auch gleich alle weiteren Anforderungen an das Server-Script zusammenfassen:

  • basiert auf php5cli den externen Programmen wget und cabextract
  • darf nur von root benutzt werden (Dateirechte setzen)
  • loggt zum Syslog mit Facility local0
  • nutzt eine Konfigurationsdatei /etc/mspatch/mspatch.conf oder /etc/msmatch.conf mit folgende Einstellungsmöglichkeiten (Angaben sind Defaultwerte):
Bei dem Konfigurationseintrag binaries handelt es sich um die Produkte/Servicpacks, für die Patches, welche heruntergeladen werden sollen. Die richtige Bezeichnung entnehmen wir der Ausgabe von mbsacli.exe. Dabei müssen alle Produktnamen angegeben werden, welche lokal verwendet werden sollen. Die Linuxkiste kennt ja die Konfiguration der Wks nicht. hostallow sind Hostadressen (z.Bsp: microsoft.com), von denen überhaupt ein Patch geladen werden darf. Mehrere Angaben für binaries oder hostallow sind durch ein Semikolon zu trennen.
  • versteht die Optionen
    • -c <configile> für eine alternative Configdatei
    • -h für help
    • -l <loglevel> setzt das Loglevel,
    • -n kein download der mssecure.xml, loggt zu stdout (für Testzwecke)
    • -u für einen Update-Run
    • -v für Version
Top

Client-Script

Das Client-Script wird zyklisch per at-Job aufgerufen. Es prüft den lokalen Rechner mit Hilfe der mbsacli.exe und der gleichen mssecure.xml, die auch der Server benutzt. Wenn neue Patches eingespielt werden müssen (auf Grund der Ausgabe von mbsacli.exe), dann bezieht das Script seine Informationen von der msclient.xml, die auf dem Server liegt. Ausgehend von den Informationen Produkt/SP und Bulletin wird ermittelt, ob das File überhaupt (Attribute ignore und file) und wie (Attribut arguments) es zu installieren ist.

Falls ein benötigter Patch nicht verfügbar ist, wird ein Fehler-Log-Eintrag generiert. Entweder hatte das Serverscript nicht alle Patches "gedownloaded", es fehlen die Installationoptionen oder es handelt sich beim Client um "nicht vorgesehene" Software.

Hier noch alle restlichen Anforderungen an das Client-Script:

  • basiert auf wsh 5.6 und dem externen Programm mbsacli.exe
  • Aufruf benötigt Administrator-Rechte (mit Dateirechten)
  • hat keine eigene Configdatei, Pfadanpassungen sind im Script selbst vorzunehmen
  • versteht die Optionen
    • /install installiert die Patches
    • /missing zeigt alle fehlenden Patches an
    • /installed zeigt alle installierten Patches an
    • /suspect zeigt alle fehleneden Patches, Hinweise und Warnungen an
    • /log:[0-7] setzt das Loglevel, default ist 5
  • Nach Installation eines Patches erfolgt kein automatischer reboot des Rechners, ein angemeldeter User wird aber dazu aufgefordert.
  • Derzeit existiert kein Protokollmechanismus. Meldungen werden auf stdout ausgegeben. In späterer Ausbaustufe soll das Log zum Syslog des Servers gesendet werden.
Top

Serverscript Beispiel

Dieses Script wird von uns nicht weiterentwickelt.

#! /usr/bin/php5
<?php

define('MSPATCH_LOG'         ,'stdout');
define('MSPATCH_LOGLEVEL'    ,5);
define('MSPATCH_CONFIG1'     ,'/etc/mspatch/mspatch.conf');
define('MSPATCH_CONFIG2'     ,'/etc/mspatch.conf');
define('MSPATCH_PATCHURI'    ,'http://go.microsoft.com/fwlink/?LinkId=18121');
define('MSPATCH_PATCHPATH'   ,'/usr/local/lib/install/mspatch');
define('MSPATCH_PATCHFILE  ' ,'mssecure.xml');
define('MSPATCH_PATCHCLIENT' ,'msclient.xml');


class patch {
	function __construct(){
		$this->loglevel    = MSPATCH_LOGLEVEL;
		$this->options     = array();
		if(!$this->init()) return;
		if(!is_null($this->get_option('u'))) $this->run_update();
	}

	#############################################################
	# FUNCTIONS FOR INIT ########################################
	#############################################################
	function init(){
		#init the parameter-handler
		$this->options = getopt('c:hl:nuv');
		if(!is_null($this->get_option('h'))) $this->run_help();
		if(!is_null($this->get_option('v'))) $this->run_version();

		/* init default variables */
		$this->logtarget        = MSPATCH_LOG;
		$this->loglevel         = MSPATCH_LOGLEVEL;
		if($this->get_option('l')) $this->loglevel = $this->get_option('l');

		$this->patchuri    = MSPATCH_PATCHURI;
		$this->patchpath   = MSPATCH_PATCHPATH;
		$this->patchfile   = MSPATCH_PATCHFILE;
		$this->patchclient = MSPATCH_PATCHCLIENT;

		$this->binaries  = array();
		$this->hostallow = array();

		$this->dom_patch  = null;
		$this->dom_client = null;
		$this->xpointer_patch  = null;
		$this->xpointer_client = null;

		$this->patchexistcount = 0;
		$this->patchfailcount  = 0;
		$this->patchignorecount= 0;

		if(!$this->read_config())      return false;
		if(!$this->test_environment()) return false;
		return true;
	}

	#############################################################
	# FUNCTIONS FOR CONFIGURATION ###############################
	#############################################################
	private function get_option($option){
		if(array_key_exists($option, $this->options)) return $this->options[$option];
		else                                          return null;
	}

	private function select_config(){
		if( $this->get_option('c')) {
			if( is_readable( $this->get_option('c')) ) {
				$this->configfile = $this->get_option('c');
				$this->log('found config '.$this->configfile, 7);
				return true;
			}
			else{
				$this->log('no config found', 3);
				return false;
			}
		}
		if(is_readable(MSPATCH_CONFIG1)){
			$this->configfile = MSPATCH_CONFIG1;
			$this->log('found config '.$this->configfile, 7);
			return true;
		}
		if(is_readable(MSPATCH_CONFIG2)){
			$this->configfile = MSPATCH_CONFIG2;
			$this->log('found config '.$this->configfile, 7);
			return true;
		}
		$this->log('no config found', 3);
		return false;
	}

	private function read_config(){
		if(! $this->select_config()) return false;
		$lines = file ($this->configfile);
		$entries = array();
		foreach($lines as $line){
			$line = trim($line);
			if(substr($line,0,1) != '#'){
				$parts = explode('=', $line,2);
				if(array_key_exists(1,$parts)) $entries[trim($parts[0])] = trim($parts[1]);
			}
		}

		/*program settings*/
		if(array_key_exists('log'       ,$entries)) $this->logtarget  = $entries['log'];
		if(array_key_exists('loglevel'  ,$entries)) $this->loglevel   = $entries['loglevel'];
		if($this->get_option('l'))                  $this->loglevel   = $this->get_option('l');

		/* system settings */
		if(array_key_exists('downloadbin'  ,$entries)) $this->downloadbin = $entries['downloadbin'   ];

		/*binary settings*/
		if(array_key_exists('patchuri'   ,$entries)) $this->patchuri    = $entries['patchuri'   ];
		if(array_key_exists('patchpath'  ,$entries)) $this->patchpath   = $entries['patchpath'  ];
		if(array_key_exists('patchfile'  ,$entries)) $this->patchfile   = $entries['patchfile'  ];
		if(array_key_exists('patchclient',$entries)) $this->patchclient = $entries['patchclient'];

		if(array_key_exists('hostallow'   ,$entries)) {
			foreach(explode(';',$entries['hostallow']) as $host){
				$host = trim($host);
				if($host) $this->hostallow[] = $host;
				$this->log('add host "'.$host.'" from config', 7);
			}
		}
		if(count($this->hostallow) < 1){
			$this->log('no allowed host found in config '.$this->configfile, 3);
			return false;
		}

		if(array_key_exists('binaries'   ,$entries)) {
			foreach(explode(';',$entries['binaries']) as $binary){
				$binary = trim($binary);
				if($binary) $this->binaries[strtoupper($binary)] = array();
				$this->log('add binary "'.$binary.'" from config', 7);
			}
		}
		if(count($this->binaries) < 1){
			$this->log('no binaries found in config '.$this->configfile, 3);
			return false;
		}
		return true;
	}

	private function test_environment(){
		if(!is_dir($this->patchpath)) {
			if( ! mkdir($this->patchpath, 0775)) {
				$this->log('can not create directory '.$this->patchpath, 3);
				return false;
			}
			else{
                chgrp ( $this->patchpath, 'adm');
				$this->log('create directory '.$this->patchpath, 4);
			}
		}
		if(!is_writable($this->patchpath)){
				$this->log('can not write in directory '.$this->patchpath, 3);
				return false;
		}

		system('which cabextract > /dev/null', $ret);
		if($ret != 0){
			$this->log('cabextract not installed', 3);
			return false;
		}
		system('which wget > /dev/null', $ret);
		if($ret != 0){
			$this->log('wget not installed', 3);
			return false;
		}
		return true;
	}

	#############################################################
	# FUNCTIONS FOR RUNNING IN ORDER OF OPTION ##################
	#############################################################
	private function run_help(){
$out = <<<EOD
mspatch
update (download) the security-patches for ms-programs
options:
	-c <configfile>     alternativ configfile
	-h                  print help
	-l <loglevel>       loglevel 0 ... 7 (emerg ... debug)
	-n                  no download the patch-informationfile, in combination with -u,
	                    logs to stdout
	                    useful to test the config and patchstatus
	-u                  update the patchfiles,
	                    use this option for a normal working run
	-v                  print version
EOD;
			echo $out."\n";
			exit;
	}

	private function run_version(){
		echo preg_replace('/.Revision: (.*) \$$/','\1','$Revision: 0.1.1.1 $')."\n";
		exit;
	}

	private function run_update(){
		$this->log('starting'.$uri, 5);
		if(!$this->download_patchfile()    ) return;
		if(!$this->read_patchfile()        ) return;
		if(!$this->find_binaries()         ) return;
		if(!$this->parse_patches()         ) return;
		if(!$this->test_clientfile()       ) return;
		if(!$this->update_clientfile()     ) return;
		if(!$this->download_patches()      ) return;
		if(!$this->check_patchconsistence()) return;
		$out = 'end with '.$this->patchexistcount.' patches';
		$out.= ' ,'.($this->patchexistcount-$this->patchignorecount-$this->patchfailcount).' ok';
		$out.= ' ,'.$this->patchignorecount.' ignored';
		$out.= ' ,'.$this->patchfailcount.' failed';
		$this->log($out,5);
	}

	#############################################################
	# FUNCTIONS FOR DOWNLOAD ####################################
	#############################################################
	private function download_patchfile(){
		if(!is_null($this->get_option('n'))) return true;
		$cabfile = 'tmp.cab';
		@unlink($this->patchpath.'/'.$cabfile);
		$this->log('download file from '.$this->patchuri, 5);
		system( 'wget -q -O '.$this->patchpath.'/'.$cabfile.' \''.$this->patchuri.'\' > /dev/null', $ret );
		if($ret != 0){
			$this->log('can not download from '.$this->patchuri, 3);
            return false;
		}
		system('cabextract '.$this->patchpath.'/'.$cabfile.' -p > '.$this->patchpath.'/'.$this->patchfile.' 2> /dev/null', $ret);
		if($ret != 0){
			$this->log('can not extract '.$cabfile.' to '.$this->patchfile, 3);
			return false;
		}
		$this->log('extract '.$cabfile.' to '.$this->patchfile, 7);
		if(! unlink($this->patchpath.'/'.$cabfile))
			$this->log('can not remove "'.$cabfile.'"', 4);
		return true;
	}

	#############################################################
	# FUNCTIONS FOR PARSE XML ###################################
	#############################################################
	function read_patchfile(){
		$this->log('read patchfile '.$this->patchfile,7);
		$this->dom_patch = @DOMDocument::load($this->patchpath.'/'.$this->patchfile);
		if(!is_object($this->dom_patch)){
			$this->log('can not open file "'.$this->patchfile.'"',3);
			return false;
		}
		$this->xpointer_patch = new domxpath($this->dom_patch);
		return true;
	}

	function find_binaries(){
		$this->log('searching IDs',7);
		foreach($this->binaries as $key => $binary){
			$parts = explode(' ', strtolower($key));
			$ServicePack = $parts[count($parts)-1];
			$Product = substr($key,0, -(strlen($ServicePack)+1));
			$this->binaries[$key]['Product'    ] = $Product;
			$this->binaries[$key]['ServicePack'] = $ServicePack;

			$xpath ='/BulletinDatastore/Products/Product';
			$xpath.='[translate(@Name,\'abcdefghijklmnopqrstuvwxyz\',\'ABCDEFGHIJKLMNOPQRSTUVWXYZ\')="'.$Product.'"]';
			$sections = $this->xpointer_patch->query($xpath);
			$ProductID = '';
			foreach($sections as $section){
				$ProductID     = $section->getAttribute('ProductID');
				$ServicePackID = $section->getAttribute('CurrentServicePackID');
			}
			if( !$ProductID  ){
				unset($this->binaries[$key]);
				$this->log('ProductID for "'.$Product.'" not found', 4);
				continue;
			}
			if( !$ServicePackID ){
				unset($this->binaries[$key]);
				$this->log('ServicePackID for "'.$ServicePackID.'" not found', 4);
				continue;
			}
			$this->binaries[$key]['ProductID'    ] = $ProductID;
			$this->binaries[$key]['ServicePackID'] = $ServicePackID;
			$this->log('found ProductID "'.$ProductID.'" for "'.$key.'" ', 7);
			$this->log('found ServicePackID "'.$ServicePackID.'" for "'.$key.'" ', 7);
		}
		if(count($this->binaries)<1){
			$this->log('can not find anyone binaries in file "'.$this->patchfile.'"', 3);
			return false;
		}
		return true;
	}

	function parse_patches(){
		$this->log('parse '.$this->patchfile.' for patches',7);
		foreach($this->binaries as $key => $binary){
			$this->binaries[$key]['patches'] = array();
			$xpath ='/BulletinDatastore/Bulletins/Bulletin/Patches/Patch';
			$xpath.='[AffectedProduct[@ProductID="'.$binary['ProductID'].'"]/AffectedServicePack[@ServicePackID="'.$binary['ServicePackID'].'"]]';
			$sections = $this->xpointer_patch->query($xpath);
			foreach($sections as $section) {
				$bulletin = $section->parentNode->parentNode->getAttribute('BulletinID');
				$noreboot = $section->getAttribute('NoReboot');
				$patchid  = $section->getAttribute('PatchLocationID');
				$uri = '';
				$xpath = '/BulletinDatastore/Locations/Location[@LocationID="'.$patchid.'"]';
				$locations = $this->xpointer_patch->query($xpath);
				foreach($locations as $location){
					$uri = $location->getAttribute('Path');
				}
				if(strtolower(substr($uri,-4)) != '.exe') $uri = '';
				$i = count($this->binaries[$key]['patches']);
				$this->binaries[$key]['patches'][$i] = array();
				$this->binaries[$key]['patches'][$i]['noreboot'] = strtoupper($noreboot);
				$this->binaries[$key]['patches'][$i]['bulletin'] = strtoupper($bulletin);
				$this->binaries[$key]['patches'][$i]['file'    ] = str_replace(' ','_',strtoupper($key.'_'.$bulletin.'.exe'));
				$this->binaries[$key]['patches'][$i]['uri'     ] = $uri;
			}
		}
		return true;
	}

	function test_clientfile(){
		$this->log('test clientfile '.$this->patchclient,7);
		if(!is_readable($this->patchpath.'/'.$this->patchclient)){
			$this->log('can\' find "'.$this->patchfile.'", generating it',5);
			$out = '<?xml version="1.0" encoding="iso-8859-1"?><patches/>';
			$fhl = fopen($this->patchpath.'/'.$this->patchclient, "w");
			fwrite($fhl, $out);
			fclose($fhl);
		}
		$this->dom_client = @DOMDocument::load($this->patchpath.'/'.$this->patchclient);
		if(!is_object($this->dom_client)){
			$this->log('can not open file "'.$this->patchclient.'"',3);
			return false;
		}
		$this->dom_client->formatOutput = true;
		$this->xpointer_client = new domxpath($this->dom_client);
		return true;
	}

	function update_clientfile(){
		$this->log('update patchinfo in '.$this->patchclient,7);

		$sections = $this->xpointer_client->query('/patches');
		foreach($sections as $section) $root = $section;

		foreach($this->binaries as $key => $binary){
			foreach( $binary['patches'] as $nr => $patch ){
				$xpath ='/patches/patch';
				$xpath.='[translate(@binary,\'abcdefghijklmnopqrstuvwxyz\',\'ABCDEFGHIJKLMNOPQRSTUVWXYZ\')="'.$key.'" and ';
				$xpath.='translate(@bulletin,\'abcdefghijklmnopqrstuvwxyz\',\'ABCDEFGHIJKLMNOPQRSTUVWXYZ\')="'.$patch['bulletin'].'"]';
				$sections = $this->xpointer_client->query($xpath);
				$exist = false;
				foreach($sections as $section){
					$exist = true;
				}
				if(!$exist){
 					$this->log('add info for '.$key.' '.$patch['bulletin'],6);
					$node = $this->dom_client->createElement('patch');
					$node = $root->appendChild($node);
					$node->setAttribute('binary'  , $key              );
					$node->setAttribute('bulletin', $patch['bulletin']);
					$node->setAttribute('ignore'  , '0'               );
					$node->setAttribute('file'    , $patch['file']    );
					$node->setAttribute('options' , ''                );
					$node->setAttribute('uri'     , $patch['uri']     );
				}
				foreach($sections as $section){
					#if($section->getAttribute('ignore') != "0") continue;
					if($patch['uri'] != '' && $patch['uri'] != $section->getAttribute('uri')){
						$section->setAttribute('uri'     , $patch['uri']);
						$section->setAttribute('options' , ''           );
	 					$this->log('updating new uri for '.$patch['file'],4);
	 					$this->log('delete options for'   .$patch['file'],7);
						if(is_writeable($this->patchpath.'/'.$patch['file'])){
		 					$this->log('delete file '.$this->patchpath.'/'.$patch['file'],5);
							unlink($this->patchpath.'/'.$patch['file']);
						}
					}
				}
			}
		}
		$out = $this->dom_client->saveXML();
		if (!$fhl = fopen($this->patchpath.'/'.$this->patchclient, "w")) {
			$this->log('can not open file for writing "'.$this->patchclient.'"', 3);
			return false;
		}
		if (!fwrite($fhl, $out)) {
			$this->log('can not write to file "'.$this->patchclient.'"', 3);
			fclose($fhl);
			return false;
		}
		fclose($fhl);
		return true;
	}


	function download_patches(){
		$this->log('download patches',7);
		$sections = $this->xpointer_client->query('/patches/patch');
		foreach($sections as $section){
			if($section->getAttribute('ignore') != "0") continue;
			if($section->getAttribute('uri') == '') continue;
			$this->log('check file '.$section->getAttribute('file'), 7);
			if(!is_readable($this->patchpath.'/'.$section->getAttribute('file'))){
				$this->log('download file from '.$section->getAttribute('uri'), 5);
				#######################################
				//check host - security
				$uriparts =  parse_url ($section->getAttribute('uri'));
				if(!isset($uriparts['host'])){
					$this->log('not a valid host: '.$section->getAttribute('uri'), 5);
					continue;
				}
				$allow = false;
				foreach($this->hostallow as $host){
					if($host == $uriparts['host']) $allow = true;
				}
				if(!$allow){
					$this->log('host '.$uriparts['host'].' not allowed, ignore', 4);
					continue;
				}
				#######################################
				system( 'wget -q -O '.$this->patchpath.'/'.$section->getAttribute('file').' \''.$section->getAttribute('uri').'\'', $ret );
				if($ret != 0){
					$this->log('can not download from '.$section->getAttribute('uri'), 4);
					@unlink( $this->patchpath.'/'.$section->getAttribute('file') );
					continue;
				}
			}
		}
		return true;
	}

	function check_patchconsistence(){
		$this->log('check patch-consistence',7);
		$sections = $this->xpointer_client->query('/patches/patch');
		foreach($sections as $section){
			$this->patchexistcount++;
			if($section->getAttribute('ignore') == "0"){
				$error = false;
				if(!is_readable($this->patchpath.'/'.$section->getAttribute('file'))){
					$this->log('file not exist '.$section->getAttribute('file'),4);
					$error = true;
				}
				if($section->getAttribute('options') == ''){
					$this->log('no options exist for '.$section->getAttribute('file'),4);
					$error = true;
				}
				if($section->getAttribute('uri') == ''){
					$this->log('no uri exist for '.$section->getAttribute('file'),4);
					$error = true;
				}
				if($error) $this->patchfailcount++;
			}
			else $this->patchignorecount++;
		}
		return true;
	}


	#############################################################
	# DEBUGGING AND LOGGING #####################################
	#############################################################
	private function log($msg, $pri = 7){
		if($pri > $this->loglevel) return;
		if($this->logtarget == 'syslog' && is_null($this->get_option('n'))) {
			define_syslog_variables();
			openlog(basename(__FILE__), LOG_CONS | LOG_PID , LOG_LOCAL0);
			syslog($pri,$msg);
			closelog();
		}
		else{
			echo $msg."\n";
		}
	}

	private function get_loglevel($pri){
		switch ($pri) {
		case 7:return 'debug'; case 6:return 'info'; case 5:return 'notice'; case 4:return 'warning';
		case 3:return 'err';   case 2:return 'crit'; case 1:return 'alert';  case 0:return 'emerg';
		default: return 'debug';
		}
	}

}
$patch = new patch();
?>

Top

Clientscript Beispiel


<?XML version="1.0" standalone="yes" ?>
<package>
	<job id="mspatch">
	<runtime >
		<named name="mspatch" helpstring="install the windows security-patches" type="simple" required="true" />
		<example>
Samples:
install the win-security patches
	cscript mspatch.wsf /install

show all missing patches
	cscript mspatch.wsf /missing

show all installed patches
	cscript mspatch.wsf /installed

show all missing patches,references and warnings
	cscript mspatch.wsf /suspect

		</example>
	<description>
	Windows Install Security-Patches Script; CScript, $Revision: 0.1 $
	Copyright GPL
	</description>
	</runtime>
<script type="text/ecmascript" >



function shell(cmd){
	var sh = new ActiveXObject("WScript.Shell"), answer = new String();
	var app = sh.Exec("%comspec% /c " + cmd);
	while (!app.StdOut.AtEndOfStream)
		answer += app.StdOut.Read(1);
	WScript.Echo (answer);
	return app.ExitCode;
}

/* functions in order of arguments */
function run(){
	if ( WScript.FullName.toLowerCase().indexOf("cscript.exe") == -1 ||
	     ! (WScript.Arguments.Named.Exists("install") ||
	        WScript.Arguments.Named.Exists("missing") ||
	        WScript.Arguments.Named.Exists("suspect") ||
	        WScript.Arguments.Named.Exists("installed"))) {
		WScript.Echo ("Bad script engine!\nUsing CScript interpreter!");
		WScript.Arguments.ShowUsage();
		WScript.Quit(1);
	}
	if(WScript.Arguments.Named.Exists("install")  ) return run_install();
	if(WScript.Arguments.Named.Exists("missing")  ) return run_missing();
	if(WScript.Arguments.Named.Exists("installed")) return run_installed();
	if(WScript.Arguments.Named.Exists("suspect"))   return run_suspect();

}

function run_missing(){
	log("run missing",7);
	if(!FSO.FileExists(PATCHPATH + "\\" + PATCHFILE)){
		log("can't find patchfile " + PATCHPATH + "\\" + PATCHFILE);
		return false;
	}
	var cmd = MBSACLI + " -hf -nvc -x " + PATCHPATH + "\\" + PATCHFILE + " -v -history 2 -s 2";
	shell(cmd);
}

function run_suspect(){
	log("run suspect",7);
	if(!FSO.FileExists(PATCHPATH + "\\" + PATCHFILE)){
		log("can't find patchfile " + PATCHPATH + "\\" + PATCHFILE);
		return false;
	}
	var cmd = MBSACLI + " -hf -nvc -x " + PATCHPATH + "\\" + PATCHFILE + " -v -history 2";
	shell(cmd);
}

function run_installed(){
	log("run installed",7);
	if(!FSO.FileExists(PATCHPATH + "\\" + PATCHFILE)){
		log("can't find patchfile " + PATCHPATH + "\\" + PATCHFILE);
		return false;
	}
	var cmd = MBSACLI + " -hf -nvc -x " + PATCHPATH + "\\" + PATCHFILE + " -history 1";
	shell(cmd);
}


function run_install(){
	log("run install",7);
	if( ! check_system()    ) return false;
	if( ! parse_tmpfile()   ) return false;
	if( ! parse_clientfile()) return false;
	if( ! install_patches() ) return false;
	return true;
}

/* function for running install */
function check_system(){
	log("check system",7);
	if(FSO.FileExists(PATCHFILEOUT)){
		FSO.DeleteFile(PATCHFILEOUT)
		log("delete old file " + PATCHFILEOUT, 7);
	}
	if(!FSO.FileExists(PATCHPATH + "\\" + PATCHFILE)){
		log("can't find patchfile " + PATCHPATH + "\\" + PATCHFILE);
		return false;
	}
	var cmd = MBSACLI + " -hf -sms -x \"" + PATCHPATH + "\\" + PATCHFILE ;
	cmd    += "\" -f \"" + PATCHFILEOUT + "\" -unicode -nvc -history 2 -s 2"
	shell(cmd);
	//mbsacli returns alltime exitcode 0, so i check it like this
	if(!FSO.FileExists(PATCHFILEOUT) ||
	    FSO.GetFile( PATCHFILEOUT).size &lt; 100){
		log("failed executing mbsa",4);
		return false;
	}
	return true;
}

function parse_tmpfile(){
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/4ead2a13-de31-487e-b826-53f132f25cbb.asp
	log("parse tmpfile",7);
	var dom = new ActiveXObject("Microsoft.XMLDOM");
	dom.setProperty("SelectionLanguage", "XPath");
	dom.load(PATCHFILEOUT);
	if (dom.parseError.errorCode != 0) {
		var myErr = dom.parseError;
		log(myErr.reason,3);
		WScript.Quit(1);
	}

	var xpath = "/ScanResults/Machine/Product/Item"
	var nodes = dom.selectNodes(xpath);
	for(var i = 0; i &lt; nodes.length; i++){
		var node_ItemType = nodes[i].selectSingleNode("ItemType");
		var itemtype = (node_ItemType)? node_ItemType.firstChild.data : "";
		if( itemtype.toUpperCase() != "MISSING" ) continue;

		var node_BulletinID = nodes[i].selectSingleNode("BulletinID");
		var bulletin = (node_BulletinID)? node_BulletinID.firstChild.data.toUpperCase() : "";
        var node_ProductName = nodes[i].parentNode.selectSingleNode("ProductName");
		var product = (node_ProductName)? node_ProductName.firstChild.data.toUpperCase() : "";
		if(!product || !bulletin) continue;
		var nr = binaries.length;
		binaries[nr] = new Array();
		binaries[nr]['binary']  = product;
		binaries[nr]['bulletin'] = bulletin;
	}
	return true;
}

function parse_clientfile(){
	log("parse clientfile",7);
	var dom = new ActiveXObject("Microsoft.XMLDOM");
	dom.setProperty("SelectionLanguage", "XPath");
	dom.load(PATCHPATH + "\\" + PATCHCLIENT);
	if (dom.parseError.errorCode != 0) {
		var myErr = dom.parseError;
		log(PATCHCLIENT + " " + myErr.reason,3);
		return false;
		//WScript.Quit(1);
	}
	for(var i = 0; i&lt; binaries.length; i++){
		var xpath = "/patches/patch"
		xpath    += "[@binary = \""+ binaries[i]['binary']+ "\" and";
		xpath    += " @bulletin = \""+ binaries[i]['bulletin']+ "\"]";
		var node = dom.selectSingleNode(xpath);
		if(!node){
			log("no patch in "+PATCHCLIENT+" for "+binaries[i]['binary']+" "+binaries[i]['bulletin'],4);
			continue;
		}
		if(node.getAttribute('ignore') != "0"){
			log("ignore "+binaries[i]['binary']+" "+binaries[i]['bulletin'],7);
			continue;
		}
		if(node.getAttribute('options') == ""){
			log("no options found for "+binaries[i]['binary']+" "+binaries[i]['bulletin'],4);
			continue;
		}
		binaries[i]['file']    = node.getAttribute('file');
		binaries[i]['options'] = node.getAttribute('options');
	}
	return true;
}

function install_patches(){
	log("install patches",7);
	var installed = 0;
	for(var i = 0; i&lt; binaries.length; i++){
		if( ! binaries[i]['file'] ) continue;
		log("installing "+ binaries[i]['file']+" "+binaries[i]['options'], 5);
		if( ! FSO.FileExists(PATCHPATH+ "\\" + binaries[i]['file']) ){
			log("file not found: " + binaries[i]['file'], 4 );
			continue;
		}
		var cmd = PATCHPATH + "\\" + binaries[i]['file'] + " " + binaries[i]['options'];
		var ret = shell(cmd);
		if(ret != 0){
			log("can't install " + binaries[i]['file']+", exitcode "+ret, 4 );
			continue;
		}
		installed++;
		WScript.Sleep(200);
	}
	if( installed > 0){
		var cmd = "net send %COMPUTERNAME% \""+installed+" new security-patches installed - please reboot the computer\"";
		shell(cmd);
	}
	log(installed+" new security-patches installed", 5);
	return true;
}

/* functions for environment */
function setEnvironment(){
	SHELL = WScript.CreateObject("WScript.Shell");
	FSO   = new ActiveXObject("Scripting.FileSystemObject");
	var WshSysEnv = SHELL.Environment("PROCESS");
	PATCHFILEOUT = WshSysEnv("TEMP") + '\\mbsa.xml';
	if(WScript.Arguments.Named.Exists("log")){
		var lvl = WScript.Arguments.Named.Item("log");
		if(lvl &gt; 0 &amp;&amp; lvl &lt; 8)
			LOGLEVEL = WScript.Arguments.Named.Item("log");
		else{
			WScript.Echo ("Bad argument syntax for /log");
			WScript.Quit(1);
		}
	}
}

function log(msg, pri){
	if(pri > LOGLEVEL ) return;
	WScript.Echo (msg);
}
//#######################################################################
//#######################################################################

var binaries = new Array();
var LOGLEVEL     = 5;
var MBSACLI      = "\\\\neptun\\appli\\win\\mbsacli.exe";
var PATCHPATH    = '\\\\neptun\\install\\mspatch';
var PATCHFILE    = 'mssecure.xml';
var PATCHCLIENT  = 'msclient.xml';

setEnvironment();
run();
WScript.Quit(0);

</script>
</job>
</package>

Top

Probleme

Führen wir mal ein Update online aus, fehlen angeblich 8 wichtige Updates wie z.Bsp. die IE6.0sp1-KB833989-x86-DEU.exe. Das ist arg verwunderlich, da ja der MBSA meint, es sei alles io. Das kann nur bedeuten, das die Patch XML bei MS nicht aktuell ist, oder aber neue weitere Techniken existieren. Das ist ein Fall für den MS Support. Die sollen mal für ihr Geld auch arbeiten.

Top
Persönliche Werkzeuge