Letztens hat ein Leser in meinem Artikel Skype Werbung entfernen einen guten Kommentar hinterlassen, hier zu sehen. Es ging darum, dass es doch sicherheitstechnisch sehr bedenklich sei, von mir entwickelte, ausführbare Dateien (seien es .exe oder .bat) zu starten. Diese gesunde Skepsis begrüße ich und versuche in meinen Artikeln doch den offenen Code und viel Hintergrundinformationen die nötige Transparenz zu schaffen, um diese Sorgen zu reduzieren. Ausführbare Dateien bleiben jedoch immer ein Risiko für Computersysteme und sollten immer mit Vorsicht behandelt werden. In meiner Kommentar-Antwort hatte ich ein paar generelle Tipps dazu gegeben:
immer kritisch und skeptisch sein (vor allem bei Downloads im Netz)
immer Virenscanner seperat (per Rechtsklick meistens) über jeden Download schicken, obwohl sie auch im Hintergrund immer aktiv aufpassen
immer Dateigrößen prüfen, ob sie zu den erwarteten Inhalten passen (Musikdateien sind selten kleiner als 1MB)
immer den kompletten Dateinamen mit all seinen Dateiendungen überprüfen (Explorer Einstellungen setzen, dass Dateiendungen angezeigt werden) und prüfen, ob die Dateiendungen zum erwarteten Dateityp passen (Bilder haben selten .exe als Dateiende)
Vorsicht vor typischen Malware-Dateitypen: .exe, .dot, .dotm (sowieso alle Office Dateitypen mit einem „m“ am Ende), .vbs und weitere
im Verdachtsfall bei Online-Diensten wie VirusTotal hochladen und schauen, was diese analysieren
Diese Liste möchte ich heute noch um ein Tool ergänzen, dass an dieser Stelle ansetzt: pestudio. pestudio ist eine Analysesoftware, die ausführbare Dateien auf Binärcode-Ebene untersuchen und alle Erkentnisse „leicht lesbar“ und übersichtlich auflisten. Dabei wird die Datei nicht ausgeführt, der Vorgang ist also gefahrenfrei. Besonders praktisch ist die Schnittstelle zum VirusTotal Dienst, die in der Software verbaut ist, sodass jede analysierte Datei auch an diesen Dienst geschickt und dort bewertet wird. Das kann auch für PC-Ottonormalnutzer besonders hilfreich sein, weil dafür nicht extra der Browser geöffnet und die VirusTotal Homepage geöffnet werden muss. pestudio kann sogar in das Rechtsklick-Kontextmenü integriert werden, eine entsprechende .reg-Datei liegt bei. (Leider haben die Entwickler hier gepfuscht: in der Reg-Datei ist der feste Pfad „C:/pestudio/pestudio.exe“ verbaut, der Weg über das Rechtsklick funktioniert also nur, wenn das Programm dort liegt oder man den Pfad der .reg vor dem Import anpasst.) Davon abgesehen eignet sich die Software jedoch eher für fortgeschrittenere PC-Anwender, da die Informationen nur mit etwas Hintergrundwissen eingeschätzt werden können.
Im folgenden habe ich mal die Dateien des Skype Posts analysiert: disableSkypeAds.bat und disarmSkypeConfigs.ps1.
Sieht erwartungsgemäßig unspannend aus – sind schließlich auch keine ausführbaren Dateien und somit werden die einzelnen Codezeilen fast fehlerfrei als Strings erkannt.
Ausführbare Dateien sind jedoch wesentlich komplexer und enthalten viel mehr Funktionalitäten, die vom Programm vielleicht auch gar nicht genutzt werden. Die .exe Datei aus meinem letzten Beitrag, dem Swords and Souls Bot, sieht beispielsweise verhältnismäßig gruselig aus: Das liegt hauptsächlich daran, wie ich mit AutoIt ausführbaren Dateien kompiliere. Beispielsweise lasse ich den Code von benötigten Bibliotheken in die .exe Datei mergen, damit ich meine Programme nicht mit zusätzlichen .dll (oder anderen) Dateien ausliefern muss. Das bläht natürlich die .exe Datei auf und plötzlich hätte diese, laut pestudio, sogar die Fähigkeit, FTP Verbindungen aufzubauen 😀
Für mich trotzdem ein spannendes Tool, mit dem ich bestimmt gerne mal ein paar fremde Dateien analysiere, die man im Netz manchmal so zugespielt bekommt 😉 Also halten wir fest: Immer schön skeptisch und vorsichtig bleiben.
Skype Werbung nervt. Über die verschiedenen Skype Versionen wurde die Werbung unterschiedlich verbaut, sodass keine Lösung zum Entfernen der Werbung für immer funktioniert. So funktioniert es in der aktuellen (7.37) sowie sicher ein paar folgenden Versionen:
Windows hosts Datei umschreiben
Öffnet einen Texteditor eurer Wahl als Administrator (z.B. im Startmneü suchen -> Rechtsklick -> Als Administrator ausführen) und öffnet darin die Datei [C]:\Windows\System32\drivers\etc\hosts Fügt dort folgende Zeilen hinzu:
Die folgenden Absätze beziehen sich auf ältere Skype Versionen. Mit diesen Methode lässt sich die Werbung nicht mehr deaktivieren. Die hosts Datei ist aktuell eine funktionierende und relativ einfach umzusetzende Lösung.
Die Werbung wird bei Skype über zwei zusammenhängende Mechanismen eingebaut und abgesichert. Sie zu entfernen erfordert, beide Mechanismen auszuhebeln.
Skype Konfigurationsdateien ändern: Im Ordner %AppData%\Skype (Win7: C:\Users\[user]\AppData\Roaming\Skype) gibt es für jeden Nutzer, der sich schon einmal an dem PC über Skype eingeloggt hat, einen Ordner, der so heißt wie der Skype Benutzer. In diesem Ordner gibt es eine config.xml, die ein paar interessante Zeilen enthält:
1101
Die enthaltenen Werte müssen natürlich nur umgekehrt werden.
Es reicht jedoch nicht, diese Datei zu ändern…
Skype Kontrollserver deaktivieren: Microsoft hat eine weitere Absicherung eingebaut, die bei jedem Start von Skype die Konfigurationsdateien auf unerlaubte Änderungen prüft und diese rückgängig macht. Dafür verbindet sich Skype bei jedem Start mit zwei Skype Servern und ändert die werberelevanten Stellen der config.xml. Diese Server müssen blockiert werden. Das geht entweder über die hosts-Datei des Windows-Systems oder – etwas sauberer – über das definieren dieser Server als nicht-vertrauenswürdige bzw. „eingeschränkte Sites“ in den Windows Internetoptionen: http://apps.skype.com https://apps.skype.com http://g.msn.com https://g.msn.com Das geht coolerweise auch relativ einfach über die Registry. Die Kombination der geblockten Server und geänderten Konfigurationsdatei sollte die Werbung aus Skype verbannen.
Tracing deaktivieren: In einem Internetpost habe ich gelesen, dass die Datei RtmPal.dll aus dem Skype-Verzeichnis hauptsächlich für das Tracing, also die Beobachtung und Analyse des Nutzers, verantwortlich sein soll. Diese kann man ohne Bedenken „deaktivieren“, also löschen bzw. umbenennen.
Mein Lösungsscript vereint alle drei Anpassungen und deaktiviert somit Werbung und Tracing in Skype:
Lösung
Ich habe eine Lösung programmiert, die Skype Werbung und Tracing per Doppelklick deaktiviert. Die Lösung funktioniert nur bei Windows, sollte sowohl bei 32 als auch 64bit laufen und Windows- sowie Skype-versionsunabhängig sein.
Der Download enthält 3 Dateien: disableSkypeAds.bat – Wird per Doppelklick aufgerufen, beendet Skype, importiert setSkypeAdDomainsAsRestricted.reg, führt disarmSkypeConfigs.ps1 aus, benennt RtmPal.dll um setSkypeAdDomainsAsRestricted.reg – definiert die Domains apps.skype.com und g.msn.com als nicht vertrauenswürdig in den Windows Internetoptionen und blockiert diese damit (sauberer als über die hosts Datei) disarmSkypeConfigs.ps1 – durchsucht das Skype Programmverzeichnis nach allen „config.xml“ Konfigurationsdateien (es gibt eine pro Skype Nutzer) und entfernt die für Werbung relevanten Stellen
Ein Doppelklick auf disableSkypeAds.bat müsste in etwa folgendes Bild zeigen:
Skype vorher und nachher, wenn alles funktioniert:
Folgender Fehler kann bei WordPress auftreten, vermutlich nur bei bestimmten Hostern, nachdem es installiert oder hosterübergreifend umgezogen wurde: Der Fehler erscheint vermutlich bei der Installation oder Aktualisierung von Plugins oder Themes.
Erklärung
Das Bild zeigt den WordPress Dialog, bei dem FTP Verbindungsinformationen benötigt werden. Das Phänomen tritt auf, wenn ihr WordPress von Hand auf euren FTP hochgeladen und danach die Installation ausgeführt habt. Dadurch ist das WordPress Core mit den Besitzerrechten eures FTP Nutzers, mit dem ihr die WordPress Dateien hochgeladen habt, versehen. Alle Tätigkeiten aus dem WordPress System heraus, also beispielweise das Installieren von Plugins über die WordPress „Plugins“ Oberfläche, werden jedoch server-intern von einem anderen Nutzer ausgeführt. Dieser Nutzer heißt je nach System oftmals PHP-User, www-User, www-data oder ähnlich. Nun fehlen dem Nutzer aber die Zugriffsrechte auf die entsprechenden Ordner.
Lösungen
Es gibt mehrere mögliche Lösungen:
1) Gebt bei der WordPress Nachfrage die FTP-Nutzerdaten ein, mit denen ihr WordPress hochgeladen habt. Den Nutzer könnt ihr, falls ihr es nicht mehr genau wisst, vielleicht mit dem WebFTP-Tool eures Hosters auslesen: Im besten Falle führt WordPress danach alle Plugin Aktionen ohne Klagen aus und ihr hört nie wieder von dem Problem. Aber dann wärt ihr wahrscheinlich auch gar nicht hier gelandet.
2) Ändert die Besitzrechte von wp-content. Nutzt dafür vom Hoster zur Verfügung gestellte Möglichkeiten der Besitzrechteveränderung (Adminbackend -> Tools). Zuerst wird der Ordner wp-content auf den PHP-User gestellt, die benötigte WordPress Funktion getestet (z.B. Plugin-Update, sollte jetzt gehen) und dann stellt ihr den Besitzer wieder zurück auf den FTP-User. Bei mir hat das die Probleme dauerhaft behoben und die Besitzrechte sind einheitlich weiterhin auf dem FTP-Nutzer.
3) Mein zweiter Tipp ist eigentlich der langfristig sicherste: installiert WordPress nicht von Hand sondern über die 1-Click-Install-Methode eures Hosters. Praktisch jeder Webhoster bietet die Installation von bekannter Software auf seinem System an, WordPress ebenfalls. Dabei richtet der Hoster ein fertig installiertes und eingerichtetes System für euch ein, meistens mit nur wenigen Klicks. Dieses System hat definitiv keine Rechteprobleme, ist nun aber frisch installiert. Je nachdem, wie jung oder alt euer Blog ist, kostet es nun weniger oder mehr Aufwand, die Inhalte in das neu installierte System zu transferieren. Aber diese Lösung ist am sichersten und wird keine zukünftigen Probleme mehr machen, ich empfehle das also. Export/Import: Der Export besteht im Grundlegenden aus 2 Typen: Daten (Beiträge, Kommentare usw), Plugins und Themes. Daten: Benutzt entweder die WordPress eigene Export-Funktion unter „Werkzeuge -> Daten exportieren“ oder exportiert eure komplette WordPress Datenbank (SQL Export via phpMyAdmin). Diesen Export dann im neuen WordPress bzw. dessen Datenbank. Plugins/Themes: Entweder ihr installiert im neuen WordPress die Komponenten von Hand (je nachdem wieviele es sind), oder ihr zieht euch direkt vom FTP die Ordner „plugins“ und „themes“ und überschreibt einfach diese Ordner in eurem neuen WordPress System. Anschließend müsste der neue WordPress Blog die Plugins, Themes (die beide ggf. noch aktiviert werden müssen) und Daten enthalten. Ich werde aber demnächst nochmal einen ausführlichen Beitrag über gute WordPress Backup Methoden schreiben und eine komplette Backup Lösung konzipieren.
4) Tragt in eure .htaccess Datei in der Root eures WordPress Blogs (also direkt im obersten Ordner, beispielsweise „/blog/“) folgende Zeile ein:
Neu in meiner Software-Deployment Auflistung ist ab jetzt KeePass 2, eine ziemliche gute und erweiterbare Passwortmanagement-Freeware. Zu KeePass selbst werde ich in Zukunft noch einige Artikel schreiben, von der Installation bis zur Integration in Browser und Smartphone. Jetzt erstmal schnell das Deployment im Windows Active Directory via Batch Script, im üblichen Stil.
Vorbereitung
Der .exe Installer von der Homepage (Pro Version) liefert beim Aufruf mit dem Parameter „/?“ eine lange Liste von Optionen zur Anpassung der Installation. Diese findet ihr hier auch nochmal zur besseren Lesbarkeit in HTML Form. Praktisch: Ihr könnt mit allen Parametern die Installation anpassen und bei der ersten Installation mit dem Parameter /SAVEINF=filename das Set an Optionen in eine Konfigurationsdatei speichern. Für die nächste Installation reicht als Parameter dann nur noch /LOADINF. So lassen sich beispielsweise unterschiedliche Deploymentprofile erstellen und nutzen.
Plugins
Eine der Stärken von KeePass ist die starke Erweiterbarkeit mit Hilfe der vielen Plugins und Tools. Die Installation und Verteilung ist bei fast allen relativ einfach: es reicht schon die entsprechende Plugin-Datei (z.B. .plgx) in das Programmverzeichnis zu legen, ggf. auch in einen seperaten „plugins“ Unterordner für eine verbesserte Übersicht. Beim nächsten Start von KeePass wird das Plugin automatisch erkannt und geladen. Aus diesem Grund kopiert das Deployment-Script benötigte Plugins einfach nur in den Installationsordner – fertig!
Deployment
Aktuell: Version 2.34 via .exe Das im Skript verwendete Programm VersionCompare ist eine Eigenprogrammierung und hier als Download verfügbar.
Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.
@echo off && color 9f && setlocal
set wd=\\lea\Deployment\Software\KeePass
set log=%wd%\keepass.log
set tools=\\lea\Deployment\Sonstiges\tools
set instversion=0.0
set versionEL=9
set regEL=999
set exepath=none
set retry=0
REM:: ######## EDIT THIS ####
set newversion=2.34
REM:: #######################
REM:: Clientfilter: nur die Computer aus der allowedPCs.txt dürfen installieren
REM::for /f %%f in (%wd%\allowedPCs.txt) do if "%computername%"=="%%f" goto check
REM::goto end
REM:: Clientfilter: die Computer aus der deniedPCs.txt dürfen nicht installieren
for /f %%f in (%wd%\deniedPCs.txt) do if "%computername%"=="%%f" goto end
:check
if exist "c:\Program Files (x86)\KeePass2\KeePass.exe" set exepath="c:\Program Files (x86)\KeePass2\KeePass.exe"
if exist "c:\Program Files\KeePass2\KeePass.exe" set exepath="c:\Program Files\KeePass2\KeePass.exe"
if %exepath%==none goto install
goto checkversion
:checkversion
for /f "tokens=1-3" %%i in ('%tools%\sigcheck %exepath%') do ( if "%%i %%j"=="File version:" set instversion=%%k )
%tools%\VersionCompare.exe %instversion% %newversion%
set versionEL=%errorlevel%
if "%versionEL%"=="-1" goto install
if "%versionEL%"=="0" echo %date% %time:~0,8% - %computername% hat bereits %instversion% installiert >> %log% & goto plugins
if "%versionEL%"=="1" echo %date% %time:~0,8% - %computername% hat bereits %instversion% (neuer) installiert >> %log% & goto plugins
:install
echo %date% %time:~0,8% - %computername% installiert... >> %log%
TASKKILL /f /im KeePass.exe
start /w %wd%\KeePass-%newversion%.exe /VERYSILENT /SP- /SUPPRESSMSGBOXES /LOG="logs/%computername%.log" /NORESTART /CLOSEAPPLICATIONS /SAVEINF="standardsetup.inf" /DIR="%ProgramFiles%\KeePass2" /GROUP="KeePass 2"
set kpel=%errorlevel%
if %kpel%==1618 goto retry REM:: msiexec process in use, installation already in progress (eg. windows updates running)
if %kpel%==1602 goto retry REM:: user canceled installation (eg. taskkill)
if %kpel%==1603 goto retry REM:: fatal error, some use it for "already installed" (eg. java)
if %kpel%==1638 goto retry REM:: another product is already installed, denies an update
echo %date% %time:~0,8% - %computername% hat Version %newversion% mit EL %kpel% abgeschlossen >> %log%
goto plugins
:plugins
REM:: copy plugins
if exist "c:\Program Files (x86)\KeePass2\KeePass.exe" xcopy /Y /S /I "%wd%\plugins" "c:\Program Files (x86)\KeePass2\plugins"
if exist "c:\Program Files\KeePass2\KeePass.exe" xcopy /Y /S /I "%wd%\plugins" "c:\Program Files\KeePass2\plugins"
goto end
:retry
if %retry%==1 goto retryfailed
echo %date% %time:~0,8% - %computername% hatte den Fehler %kpel%, retry in 500Sek... >> %log%
set retry=1
REM:: 5 Minuten warten
ping localhost -n 500 > nul
goto install
:retryfailed
echo _!_ %date% %time:~0,8% - %computername% hat die Installation abgebrochen, RETRY FAILED! >> %log%
goto end
:end
endlocal
exit
Dieser Beitrag ist eine Ergänzung bzw. Erweiterung des vorherigen Posts „FTP-Backup-Lösung mit PHP“. Die große Neuerung der Version 1.2 ist die Funktion MySQL Datenbanken sichern zu können. Auch hier wird ein Backup erstellt, überschüssige Backups (wenn mehr vorhanden sind als der gewünschte Maximalwert) werden gelöscht und neue Backups ggf. zu einem externen Server übertragen. Mit Version 1.2.1 gibt es zusätzlich die Möglichkeit, ALLE Ordner der Root-Ebene, mit Ausnahmen, zu sichern und Version 1.2.2 ermöglicht detailliertere Ausnahmen. In Version 1.3 wird das Backup mittels eines weiteren Skripts aufgerufen. Mehr dazu weiter unten.
Features
Diese Lösung (v1.3) bietet nun folgenden Funktionsumfang:
beliebig viele Ordner des All-Inkl Accounts in einzelne .tar.gz Archive sichern
oder: alle Ordner der Root-Ebene, mit möglichen Ausnahmen, sichern
Detailliertere Ausnahmen mit Datei- und Ordnermasken wie z.B. „*.tar.gz“
Einschränkung der Anzahl aufgehobener Backups – älteste Backups werden automatisch gelöscht
detaillierte Ausgabe inklusive benötigter Zeit
E-Mail Benachrichtigung
Farbliche Hervorhebung
Verbesserungen des Backup Prozesses, zusätzliche Überprüfungen und Debug Infos bei Fehlern
Verbinden eines externen FTP Server und Kopieren aller neuen Backups
Angabe eines beliebigen FTP Ports
Verbindung über FTPs (SSL FTP) Port 21 wird verwendet, unsicheres FTP nur noch als Fallback
detailliertere Informationen über die Backups in der Benachrichtigungsmail
Backup von beliebig vielen MySQL Datenbanken von localhost, Aufräumen der Backups und Export an externen Server
E-Mail Anpassungen über Parameter möglich – Betreff, Anmerkungen, Details
ausführliche Ausgabe aller Backups im Skript und per Mail
Zwischen den Zeilen 37 und 82 findet ihr alle Variablen, die ihr anpassen müsst/könnt.
Zur Datenbanksicherung ist zu sagen, dass diese auf den Hoster All-Inkl optimiert ist. Sie sichert nur Datenbanken von localhost und benötigt den PHP Befehl „exec()“ sowie die Komponenten „mysqldump“ und „gzip“, die auf All-Inkl Servern erlaubt bzw. installiert sind. Auf anderen Hostern müssen daher ggf. diese Möglichkeiten geschaffen oder die MySQL Sicherung (Zeile 190-191) verändert werden.
Update 03/2020: Blogleser Crunchy hat mich netterweise darauf hingewiesen, dass durch den Wegfall des apache-mode die Skriptausführungszeit nun auf 10 Minuten begrenzt ist. Daher ist es nun sinnvoll, das Backupskript über ein zweites Skript aufzurufen. All-Inkl gibt das Format grob vor und ich habe unten im Download ein solches Aufruf-Skript auch mit eingefügt. Ladet euch also beide Skripte und richtet euch einen Cronjob mit dem Aufruf-Skript init-backup.php ein.
Screenshot
Code
Schaut für Code-Alternativen oder ein weniger komplexes System auch auf die Version 1.1 und 1.0
Das Verzeichnis, in dem die backup.php und die Backups liegen, sollte natürlich mit einer .htpasswd abgesichert werden. Mit einer eingerichteten .htpasswd Datei ist zuerst ein Login nötig, eh man auf bestimmte Bereiche des Webspaces zugreifen darf: Die Datei .htpasswd enthält hierbei die Login Daten und in der .htaccess des Backup Unterordners wird festgelegt, dass eine .htpasswd diesen Ordner schützt. Die .htpasswd generiert ihr euch am besten mit diesem Generator und baut sie dann folgendermaßen in die .htaccess dieses Ordners ein:
Da wir schonmal bei .htaccess sind, erhöhen wir die Sicherheit mit ein paar weiteren grundlegenden Zeilen:
#block access to certain file types
<FilesMatch ".(htaccess|htpasswd|ini|phps|log|sh|tar.gz)
quot;> Order Allow,Deny Deny from all </FilesMatch>
# disable directory browsing Options All -Indexes
# prevent basic url hacking stuff # from: http://www.queness.com/post/5421/17-useful-htaccess-tricks-and-tips RewriteEngine On # proc/self/environ? no way! RewriteCond %{QUERY_STRING} proc/self/environ [OR] # Block out any script trying to set a mosConfig value through the URL RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR] # Block out any script trying to base64_encode crap to send via URL RewriteCond %{QUERY_STRING} base64_encode.*(.*) [OR] # Block out any script that includes a <script> tag in URL RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR] # Block out any script trying to set a PHP GLOBALS variable via URL RewriteCond %{QUERY_STRING} GLOBALS(=|[|\%[0-9A-Z]{0,2}) [OR] # Block out any script trying to modify a _REQUEST variable via URL RewriteCond %{QUERY_STRING} _REQUEST(=|[|\%[0-9A-Z]{0,2}) # Send all blocked request to homepage with 403 Forbidden error! RewriteRule ^(.*)$ /index.htm [F,L]
ErrorDocument 401 /backup/index.htm ErrorDocument 403 /backup/index.htm ErrorDocument 404 /backup/index.htm ErrorDocument 500 /backup/index.htm Dadurch werden Zugriffe auf bestimmte Dateitypen (auch die Backup Dateien), Verzeichnisse und Zugriffe mit sicherheitskritischen Merkmalen unterbunden. Alle diese nicht validen Zugriffe bekommen die index.htm serviert, welches einfach nur eine leere HTML Datei ist. Somit wird den Abfragenden auch kein detaillierter Grund gegeben, warum der Zugriff fehlschlug.
Automatisierung mit All-Inkl Cronjobs
Zu guter Letzt hilft diese Sicherungslösung natürlich nur, wenn sie automatisiert wird. Auch dies ist stark abhängig von eurem Hoster, System, dem Anwendungsbereich usw. Im Falle von All-Inkl als Webhoster, könnt ihr die Cronjob Funktionalität im KAS (KAS -> Tools -> Cronjobs) benutzen:
ACHTUNG: Richtet euch den Cronjob bitte für die init-backup.php ein, nicht für die backup.php. Siehe Update von 03/2020 oben.
Verbesserungen des Backup Prozesses, zusätzliche Überprüfungen und Debug Infos bei Fehlern
Verbinden eines externen FTP Server und Kopieren aller neuen Backups
Angabe eines beliebigen Ports
Verbindung über FTPs (SSL FTP) Port 21 wird verwendet, unsicheres FTP nur noch als Fallback
detailliertere Informationen über die Backups in der Benachrichtigungsmail
Nun werden also von beliebig vielen Ordner des FTP-Root Backups erstellt, gegebenenfalls aufgeräumt wenn mehr Backups existieren als aufgehoben werden sollen und anschließend alle neuen Backups auf einen externen FTP Server kopiert. Für den Upload wird der passive FTP Modus verwendet, da dieser in den seltensten Fällen Probleme macht. Sollte der Wechsel zum passiven FTP fehlschlagen, wird dennoch aktives FTP probiert. Alle nötigen Informationen werden in den Variablen in Zeile 16 bis 25 angegeben. Format und Hilfe steht jeweils dabei, eigentlich sollte da alles klar sein.
Die Erweiterungen machen aus dem Skript eine beispielhafte Backup-Lösung für FTP Inhalte. Auch hier am Beispiel von All-Inkl als Hoster. Wer die Lösung unabhängig von All-Inkl einsetzen möchte, wird das Archive_Tar PHP Modul und irgendeine Art von Cronjob-Funktionalität brauchen.
Update (02.06.2015): Anpassung der Variablennamen zur besseren Lesbarkeit, Fehlerkorrekturen, verbesserte FTP-URI-Verarbeitung, Portangabe möglich, Verbindung über FTPs (FTP über SSL über Port 21) möglich (mit Fallback zu unsicherem FTP wenn FTPs nicht funktioniert), erweiterte Informationen über die erfolgten Backups in der Benachrichtigungsmail, kleine Optimierungen Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.
<?
// PHP-Konfiguration optimieren
// if no errors are shown, please check htaccess restrictions by "php_flag display_errors off"
// in this or parent folders
@error_reporting(E_ALL);
@ini_set("max_execution_time", 300);
@ini_set("memory_limit", "256M");
header('Content-Type: text/html; charset=utf-8');
include "Archive/Tar.php";
$pfad = preg_replace('/(\/www\/htdocs\/\w+\/).*/', '$1', realpath(__FILE__));
$alltime = 0;
$newbackups = array();
$backupinfo = array();
echo "<style>.ok{color:#478F47;}.err{color:#DA3833;}.grey{color:grey;}.warn{color:#f0ad4e;}th,td{border-bottom: 1px solid #aaa;}</style>";
// ########## EDIT THIS VARIABLES ###################
$foldertobackup = array("tools", "reports"); // which root folders should get backed up?
$copytoexternalftp = 1; // copy new backup files to external ftp server? should be 1/"yes"/"ja" or 0/"no"/"nein"
// external (ftp) servers to copy new backups to, format:
// in general: ftp://username:password@url:port/path (port is required!)
// ftp://user:pw@ftp.server.com:21/
// ftp://user:pw@serverurl.com:21/optional/path
// secure sftp connection (port 22) in preparation but not ready yet
$externalftpuri = "ftp://admin:password@firma.dns.com:21/Data/FTP-Backups";
$backupfilemaximum = 2; // how many archives should be stored?
$dir = $pfad."backup/"; // in which subfolder is this backup php file? this would be: "root/backup/"
$sendmail = 1; // send notification mail when all backups are done - should be 1/"yes"/"ja" or 0/"no"/"nein"
$sendmailto = "adminmail@firma.de"; // valid mail address to send the mail to
// ##################################################
echo "<span class='ok'>".date("d.m.Y G:i:s")."</span><br>";
foreach ($foldertobackup as $verzeichnis) {
$jobtime = time();
echo "<br><br>########################################<br>";
echo "<strong>Verzeichnis $verzeichnis wird gesichert...</strong><br>";
flush();
// check if folder exists
if(!file_exists($pfad.$verzeichnis)) {
echo "<span class='err'>Sicherung fehlgeschlagen. Zu sichernder Ordner $pfad$verzeichnis existiert nicht.</span>";
continue;
}
// Name: [verzeichnis]_[Datum]_[Uhrzeit].tar.gz
$archivname = $verzeichnis.date('_Y-m-d_His').".tar.gz";
// Name: [All-Inkl-Accountname]_[Datum]_[Uhrzeit].tar.gz
//$archivname = preg_replace('/.+\/(.+)\/$/', '$1', $pfad).date('_Y-m-d_His').".tar.gz";
// Auszuschließende Ressourcen
$ignorieren = array("*.sql.gz", "*.tar.gz", "usage", "logs");
// ######### create backup
$archiv = new Archive_Tar($archivname, true);
$archiv->setIgnoreList($ignorieren);
$archiv->createModify($pfad.$verzeichnis, "", $pfad);
$backuptime = time() - $jobtime;
$archivsize = round(filesize($dir.$archivname)/1000000);
if (!is_numeric($archivsize)) {
$archivsize = "filesize error";
}
if (is_int($backuptime)) {
echo "<span class='ok'>Backup fertig: $archivname (Größe: $archivsize MB, Dauer: $backuptime Sekunden)</span><br>";
} else {
echo "<span class='ok'>Backup fertig: $archivname</span><br>";
}
// check created archive
if (!is_object($archiv)==1 || !is_numeric($archivsize) || !$archivsize>50) {
// abort process due to wrong type or too small filesize (which likely is an error)
echo "<span class='err'>Sicherung fehlgeschlagen. Erstelltes Archiv ist fehlerhaft.</span><br>Mehr Infos <a href='http://hannes-schurig.de/09/05/2015/ftp-backup-skript-in-php/' target='blank'>hier</a><br>";
// debug
echo "<p class='grey'>Debug:<br>";
echo "Pfad: $dir.$archivname<br>";
echo "Typ: ".gettype($archiv)."<br>";
echo "Größe: $archivsize MB</p>";
continue;
}
$newbackups[$archivname] = $dir.$archivname;
echo "Aufräumen der Backups...<br>";
flush();
// integer starts at 0 before counting
$i = 0;
$backupfiles = array();
// ######### collect valid backup files
if ($handle = opendir($dir)) {
while (($file = readdir($handle)) !== false) {
if ( is_int(strpos($file, $verzeichnis)) == true &&
preg_match('/\.tar.gz$/i', $file) &&
!in_array($file, array('.', '..')) &&
!is_dir($dir.$file)
) {
$backupfiles[$dir.$file] = filectime($dir.$file);
}
}
}
echo count($backupfiles)." valide Backups dieses Ordners gefunden, ";
echo "$backupfilemaximum Backups sollen behalten werden. ";
$backupcountdif = count($backupfiles)-$backupfilemaximum;
if ($backupcountdif<=0) {
echo "Kein Backup wird gelöscht.<br>";
} else if ($backupcountdif==1) {
echo "1 Backup wird gelöscht:<br>";
} else if ($backupcountdif>=2) {
echo "$backupcountdif Backups werden gelöscht:<br>";
}
flush();
// ######### sort and delete oldest backups
// sort backup files by date
arsort($backupfiles);
// reset counter variable
$i = 0;
// delete oldest files
foreach ($backupfiles as $filepath => $value) {
if($i>=$backupfilemaximum) {
echo "$filepath wird gelöscht...<br>";
if (unlink($filepath)) {
echo "<span class='ok'>Datei erfolgreich gelöscht.</span><br>";
} else {
echo "<span class='err'>Fehler beim Löschen der Datei.</span><br>";
}
}
$i++;
}
$jobendtime = time() - $jobtime;
array_push($backupinfo, array($verzeichnis, $archivname, $archivsize." MB", $jobendtime." Sekunden", date("d.m.Y G:i:s")));
echo "<span class='ok'>Backup für Verzeichnis $verzeichnis abgeschlossen.</span><br>";
if (is_int($jobendtime)) {
echo "######################################## (Dauer: $jobendtime Sekunden)<br>";
$alltime += $jobendtime;
} else {
echo "########################################<br>";
}
}
if(count($newbackups)>0) {
echo "<br><span class='ok'>Die automatische Sicherung des Skripts '".pathinfo(__FILE__, PATHINFO_BASENAME)."' hat ".count($newbackups)." Verzeichnisse in insgesamt $alltime Sekunden gesichert.</span><br><br>";
} else {
echo "<br><span class='err'>Es scheint leider so als wenn keine Backups erfolgreich erstellt wurden.</span><br><br>";
}
flush();
// ######### copy backups to external storages
if (!isset($copytoexternalftp) || $copytoexternalftp== 0 && in_array($copytoexternalftp, array("no", "nein"))) {
echo "<span class='warn'>Backups werden nicht auf einen externen FTP kopiert. Option ist deaktiviert.</span><br>";
} else {
$ftptime = time();
echo "########################################<br>";
echo "<strong>".count($newbackups)." Backups werden auf externen FTP kopiert...</strong><br>";
flush();
$ftpcon = getFtpConnectionByURI($externalftpuri);
flush();
if(gettype($ftpcon)=="resource") {
foreach ($newbackups as $filename => $fullpath) {
$uploadtime = time();
echo "Kopiere .$filename. (Größe: $archivsize MB) auf den FTP...<br>";
flush();
if (ftp_put($ftpcon, $filename, $filename, FTP_ASCII)) {
$uploadendtime = time() - $uploadtime;
if (is_int($uploadendtime)) {
echo "<span class='ok'>Backup erfolgreich kopiert. (Dauer: $uploadendtime Sekunden)</span><br>";
} else {
echo "<span class='ok'>Backup erfolgreich kopiert.</span><br>";
}
} else {
echo "<span class='err'>Fehler beim Kopieren des Backups.</span><br>";
}
flush();
}
}
$ftpendtime = time() - $ftptime;
if (is_int($ftptime)) {
echo "######################################## (Dauer: $ftpendtime Sekunden)<br>";
} else {
echo "########################################<br>";
}
}
flush();
// ######### send mail
if (!isset($sendmail) || $sendmail== 0 && in_array($sendmail, array("no", "nein"))) {
echo "<br><span class='warn'>Benachrichtigungsmail wurde nicht verschickt. Option ist deaktiviert.</span><br>";
} else {
if(!preg_match( '/^([a-zA-Z0-9])+([.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-]+)+/' , $sendmailto)) {
echo "<br><span class='err'>FEHLER: Mail konnte nicht versendet werden, da die Adresse ungültig ist!</span><br>";
} else {
$mailsubject = "Automatische FTP Sicherung abgeschlossen";
$mailtext = "Die automatische Sicherung des FTP-PHP-Backup-Skripts ".pathinfo(__FILE__, PATHINFO_BASENAME)." hat ".count($foldertobackup)." Verzeichnisse in insgesamt $alltime Sekunden gesichert.<br><br>";
// add backup informations as table to mailtext
$mailtext .= "<table border=0 style='border-spacing:0;'><thead><tr><th>Verzeichnis</th><th>Dateiname</th><th>Größe</th><th>Dauer</th><th>Timestamp</th></tr></thead><tbody>";
for($i=0;$i<count($backupinfo);$i++) {
$mailtext .= "<tr>";
for($j=0;$j<count($backupinfo[$i]);$j++) {
$mailtext .= "<td>" . $backupinfo[$i][$j] . "</td>";
}
$mailtext .="</tr>";
}
$mailtext .= "</tbody></table>";
mail(
$sendmailto,
$mailsubject,
$mailtext,
"From: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Reply-To: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Content-Type: text/html\r\n"
) or die("<br><span class='err'>FEHLER: Mail konnte wegen eines unbekannten Fehlers nicht versendet werden.</span><br>");
echo "<br><span class='ok'>Benachrichtigungsmail wurde erfolgreich verschickt!</span><br>";
}
}
flush();
// function to get ftp connection object from URI
// basics were from: http://php.net/manual/de/function.ftp-connect.php#89811
function getFtpConnectionByURI($uri)
{
// Split FTP URI into:
// $match[0] = ftp://admin:password@barketing.dns.com:21/Data/FTP-Backups
// $match[1] = ftp
// $match[2] = admin
// $match[3] = password
// $match[4] = barketing.dns.com
// $match[5] = 21
// $match[6] = /Data/FTP-Backups
preg_match("/([a-z]*?):\/\/(.*?):(.*?)@(.*?):(.*?)(\/.*)/i", $uri, $match);
$ftpcon = null;
// check if port is set and uri is formatted correctly
if(is_int(intval($match[5]))) {
$port = intval($match[5]);
// check if ftp(s) or sftp is chosen
if($match[1]=="ftp") {
// set up and ftp(s) connection, login
echo "Stelle (FTPs über SSL - Port $port) Verbindung zu FTP Server $match[4] her...<br>";
// try ftps over ssl, usally through port 21
$ftpcon = ftp_ssl_connect($match[4], $port, 30);
if (!gettype($ftpcon)=="resource") {
echo "<span class='warn'>FTP über SSL - Port $port - Verbindung fehlgeschlagen!</span><br>";
echo "Stelle (unsicheres FTP - Port $port) Verbindung zu FTP Server $match[4] her...<br>";
// try normal insecure ftp
$ftpcon = ftp_connect($match[4], $port, 30);
}
if (gettype($ftpcon)=="resource") {
$login = ftp_login($ftpcon, $match[2], $match[3]);
$pasv = ftp_pasv($ftpcon, true);
}
} else if($match[1]=="sftp") {
echo "<span class='err'>SFTP Unterstützung noch nicht implementiert.</span><br>";
// if(!$port==22) {
// echo "<span class='warn'>SFTP Übertragung aber Port ist nicht 22. Ist der gewählte Port $port korrekt?</span><br>";
// }
// echo "Stelle (sichere sFTP - Port $port) Verbindung zu FTP Server $match[4]$match[5]:$match[6] her...<br>";
// $ftpcon = ssh2_connect($match[4], $port, 30);
// ssh2_auth_password($ftpcon, $match[2], $match[3]);
// $sftp = ssh2_sftp($ftpcon);
} else {
echo "<span class='err'>Kein gültiger Verbindungstyp (ftp/sftp) angegeben.</span><br>";
}
} else {
echo "<span class='err'>Der Port ist fehlerhaft angegeben. Bitte URI prüfen.</span><br>";
}
if ($ftpcon && gettype($ftpcon)=="resource")
{
echo "<span class='ok'>Verbindung hergestellt</span>";
if ($login) {
echo "<span class='ok'>, Login erfolgreich</span>";
if ($pasv) {
echo "<span class='ok'>, passiver Modus aktiviert</span>";
if(!isset($match[6]) || $match[6] == "") {
echo ".<br>";
return $ftpcon;
} else if (ftp_chdir($ftpcon, $match[6])) {
echo "<span class='ok'>, Verzeichniswechsel zu $match[6] erfolgreich.</span><br>";
return $ftpcon;
} else {
echo "<span class='err'>, Verzeichniswechsel zu $match[6] fehlerhaft.</span><br>";
return null;
}
} else {
echo "<span class='err'>, passiver Modus konnte nicht aktiviert werden. Upload wird trotzdem probiert.</span><br>";
return $ftpcon;
}
} else {
echo "<span class='err'>, Login fehlgeschlagen.</span><br>";
return null;
}
}
echo "<span class='err'>Fehler beim Verbinden mit dem FTP Server $match[4]$match[5]$match[6].</span><br>";
echo "<p class='grey'>Debug:<br>";
echo "URI (komplett): $match[0]<br>";
echo "Typ: $match[0]<br>";
echo "URI ohne Typ: $match[4]<br>";
echo "Username: $match[2]<br>";
echo "Passwort: $match[3]<br>";
echo "Port: $match[5]<br>";
echo "Unterordner: $match[6]<br>";
echo "</p>";
// Or retun null
return null;
}
// from: http://stackoverflow.com/a/10473026/516047
function startsWith($haystack, $needle) {
// search backwards starting from haystack length characters from the end
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
}
?>
Sicherheit: Absicherung mit .htpasswd
Das Verzeichnis, in dem die backup.php und die Backups liegen, sollte natürlich mit einer .htpasswd abgesichert werden. Mit einer eingerichteten .htpasswd Datei ist zuerst ein Login nötig, eh man auf bestimmte Bereiche des Webspaces zugreifen darf: Die Datei .htpasswd enthält hierbei die Login Daten und in der .htaccess des Backup Unterordners wird festgelegt, dass eine .htpasswd diesen Ordner schützt. Die .htpasswd generiert ihr euch am besten mit diesem Generator und baut sie dann folgendermaßen in die .htaccess dieses Ordners ein:
Da wir schonmal bei .htaccess sind, erhöhen wir die Sicherheit mit ein paar weiteren grundlegenden Zeilen:
#block access to certain file types
<FilesMatch ".(htaccess|htpasswd|ini|phps|log|sh|tar.gz)$">
Order Allow,Deny
Deny from all
</FilesMatch>
# disable directory browsing
Options All -Indexes
# prevent basic url hacking stuff
# from: http://www.queness.com/post/5421/17-useful-htaccess-tricks-and-tips
RewriteEngine On
# proc/self/environ? no way!
RewriteCond %{QUERY_STRING} proc/self/environ [OR]
# Block out any script trying to set a mosConfig value through the URL
RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR]
# Block out any script trying to base64_encode crap to send via URL
RewriteCond %{QUERY_STRING} base64_encode.*(.*) [OR]
# Block out any script that includes a <script> tag in URL
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR]
# Block out any script trying to set a PHP GLOBALS variable via URL
RewriteCond %{QUERY_STRING} GLOBALS(=|[|\%[0-9A-Z]{0,2}) [OR]
# Block out any script trying to modify a _REQUEST variable via URL
RewriteCond %{QUERY_STRING} _REQUEST(=|[|\%[0-9A-Z]{0,2})
# Send all blocked request to homepage with 403 Forbidden error!
RewriteRule ^(.*)$ /index.htm [F,L]
ErrorDocument 401 /backup/index.htm
ErrorDocument 403 /backup/index.htm
ErrorDocument 404 /backup/index.htm
ErrorDocument 500 /backup/index.htm
Dadurch werden Zugriffe auf bestimmte Dateitypen (auch die Backup Dateien), Verzeichnisse und Zugriffe mit sicherheitskritischen Merkmalen unterbunden. Alle diese nicht validen Zugriffe bekommen die index.htm serviert, welches einfach nur eine leere HTML Datei ist. Somit wird den Abfragenden auch kein detaillierter Grund gegeben, warum der Zugriff fehlschlug.
Automatisierung mit All-Inkl Cronjobs
Zu guter Letzt hilft diese Sicherungslösung natürlich nur, wenn sie automatisiert wird. Auch dies ist stark abhängig von eurem Hoster, System, dem Anwendungsbereich usw. Im Falle von All-Inkl als Webhoster, könnt ihr die Cronjob Funktionalität im KAS (KAS -> Tools -> Cronjobs) benutzen:
All-Inkl bietet sogar das Zusenden aller Skriptausgaben. Ihr erhaltet also zusätzlich zu dem kurzen E-Mail-Bericht, der im Skript generiert werden kann, noch eine weitere Mail mit allen Ausgaben. Diese sind dann zwar nicht mehr farbig, aber was solls:
Danke an Kenny für das wertvolle Feedback im letzten Artikel, das dazu beigetragen hat, dass ich diese Erweiterung nochmal gepostet habe. Meinst du, dass das jetzt eine Basic Backup Lösung sein könnte? Das einzige, was noch fehlt, ist das Backup Management auf dem externen FTP Server, damit der nicht überläuft. Aber das ist nun wirklich Aufgabe der Admins zu entscheiden und zu verwalten, wie lange Backups aufgehoben werden sollen.
Achtung: Diese Backup Lösung habe ich im nächsten Artikel noch einmal stark erweitert. Ich empfehle diese komplexere Lösung zu benutzen und die darin enthaltenen Sicherheitshinweise zu beachten.
FTP Backups (bisher)
Mein Blog läuft auf dem Hoster ALL-INKL.com, den ich nebenbei gesagt nach mehreren Jahren immernoch empfehlen kann. Bisher habe ich halbwegs (un)regelmäßig manuell, von Hand, FTP Backups erstellt. Also alle paar Monate habe ich über einen FTP Tool wie FileZilla bestimmte Ordner heruntergeladen, Datei für Datei, anschließend in ein Archiv gepackt und irgendwo verstaut. Dieser Prozess ist natürlich in vielerlei Hinsicht nicht zuverlässig, zeitintensiv und aufwändig. Auch das Sichern der Ordner automatisiert mit Tools wie SyncBack(mein Artikel dazu) wird immer zeitintensiver, je größer die zu sichernde Datenmenge wird. Außerdem muss immer ein PC, auf dem die Tools laufen, an sein.
FTP Backups via PHP Skript
Sinnvoller ist es, diese Backups auf dem Server des Webhosters erstellen zu lassen. So wird der heimische PC nicht belastet. All-Inkl bietet dafür ein recht einfaches PHP Skript zum Sichern eines einzelnen FTP Ordners oder des gesamten All-Inkl FTP Accounts.
Ich habe dieses Skript ausgebaut und um verschiedene Funktionen ergänzt:
beliebig viele Ordner des All-Inkl Accounts in einzelne .tar.gz Archive sichern
Einschränkung der Anzahl aufgehobener Backups – älteste Backups werden automatisch gelöscht
detaillierte Ausgabe inklusive benötigter Zeit
E-Mail Benachrichtigung
Screenshot
Code
Achtung: Dieses Backup Skript habe ich in Version 1.1 noch einmal stark erweitert. Ich empfehle diese komplexere Lösung zu benutzen. Besser wäre sogar die Version 1.2, mit der sich zusätzlich MySQL Datenbanken mitsichern lassen.
Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.
<?
// PHP-Konfiguration optimieren
// if no errors are shown, please check htaccess restrictions by "php_flag display_errors off"
// in this or parent folders
@error_reporting(E_ALL);
@ini_set("max_execution_time", 300);
@ini_set("memory_limit", "256M");
header('Content-Type: text/html; charset=utf-8');
include "Archive/Tar.php";
$pfad = preg_replace('/(\/www\/htdocs\/\w+\/).*/', '$1', realpath(__FILE__));
$alltime = 0;
// ########## EDIT THIS VARIABLES ###################
$foldertobackup = array("bonnie", "tests", "locationmap", "blog"); // which root folders should get backed up?
$backupfilemaximum = 2; // how many archives should be stored?
$dir = $pfad."backup/"; // in which subfolder is this backup php file? this would be: "root/backup/"
$sendmail = 1; // send notification mail when all backups are done - should be 1/"yes"/"ja" or 0/"no"/"nein"
$sendmailto = "admin@yourdomain.com"; // valid mail address to send the mail to
// ##################################################
foreach ($foldertobackup as $verzeichnis) {
$jobtime = time();
echo "<br><br>########################################<br>";
echo "<strong>Verzeichnis ".$verzeichnis." wird gesichert...</strong><br>";
flush();
// Name: [verzeichnis]_[Datum]_[Uhrzeit].tar.gz
$archivname = $verzeichnis.date('_Y-m-d_His').".tar.gz";
// Name: [All-Inkl-Accountname]_[Datum]_[Uhrzeit].tar.gz
//$archivname = preg_replace('/.+\/(.+)\/$/', '$1', $pfad).date('_Y-m-d_His').".tar.gz";
// Auszuschließende Ressourcen
$ignorieren = array("*.sql.gz", "*.tar.gz", "usage", "logs");
// ######### create backup
$archiv = new Archive_Tar($archivname, true);
$archiv->setIgnoreList($ignorieren);
$archiv->createModify($pfad.$verzeichnis, "", $pfad);
$backuptime = time() - $jobtime;
if (is_int($backuptime)) {
echo "Backup fertig: ".$archivname." (Dauer: ".$backuptime." Sekunden)<br>";
} else {
echo "Backup fertig: ".$archivname."<br>";
}
echo "Aufräumen der Backups...<br>";
flush();
// integer starts at 0 before counting
$i = 0;
$backupfiles = array();
// ######### collect valid backup files
if ($handle = opendir($dir)) {
while (($file = readdir($handle)) !== false) {
if ( is_int(strpos($file, $verzeichnis)) == true &&
preg_match('/\.tar.gz$/i', $file) &&
!in_array($file, array('.', '..')) &&
!is_dir($dir.$file)
) {
$backupfiles[$dir.$file] = filectime($dir.$file);
}
}
}
echo count($backupfiles)." valide Backups dieses Ordners gefunden, ";
echo $backupfilemaximum." Backups sollen behalten werden. ";
$backupcountdif = count($backupfiles)-$backupfilemaximum;
if ($backupcountdif<=0) {
echo "Kein Backup wird gelöscht.<br>";
} else if ($backupcountdif==1) {
echo "1 Backup wird gelöscht:<br>";
} else if ($backupcountdif>=2) {
echo $backupcountdif." Backups werden gelöscht:<br>";
}
flush();
// ######### sort and delete oldest backups
// sort backup files by date
arsort($backupfiles);
// reset counter variable
$i = 0;
// delete oldest files
foreach ($backupfiles as $key => $value) {
if($i>=$backupfilemaximum) {
echo $key." wird gelöscht...<br>";
if (unlink($key)) {
echo "Datei erfolgreich gelöscht.<br>";
} else {
echo "Fehler beim Löschen der Datei.<br>";
}
}
$i++;
}
$jobendtime = time() - $jobtime;
if (is_int($jobendtime)) {
echo "######################################## (Dauer: ".$jobendtime." Sekunden)<br>";
$alltime += $jobendtime;
} else {
echo "########################################<br>";
}
}
echo "<br><br>Die automatische Sicherung des FTP-PHP-Backup-Skripts '".pathinfo(__FILE__, PATHINFO_BASENAME)."' hat ".count($foldertobackup)." Verzeichnisse in insgesamt ".$alltime." Sekunden gesichert.<br><br>";
// ######### send mail
if (!isset($sendmail) || $sendmail== 0 && in_array($sendmail, array("no", "nein"))) {
echo "Benachrichtigungsmail wurde nicht verschickt.";
} else {
if(!preg_match( '/^([a-zA-Z0-9])+([.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-]+)+/' , $sendmailto)) {
echo "FEHLER: Mail konnte nicht versendet werden, da die Adresse ungültig ist!";
} else {
mail(
$sendmailto,
"Automatische FTP Sicherung abgeschlossen",
"Die automatische Sicherung des FTP-PHP-Backup-Skripts ".pathinfo(__FILE__, PATHINFO_BASENAME)." hat ".count($foldertobackup)." Verzeichnisse in insgesamt ".$alltime." Sekunden gesichert.",
"From: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Reply-To: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Content-Type: text/html\r\n"
) or die("FEHLER: Mail konnte wegen eines unbekannten Fehlers nicht versendet werden");
echo "Benachrichtigungsmail wurde erfolgreich verschickt!";
}
}
?>
Wichtige Anmerkungen
Bitte beachtet, dass einige Zeilen angepasst werden müssen und der Code nur bei dem Webhoster All-Inkl getestet wurde. Die Zeile
include "Archive/Tar.php"
, die ein externes Modul einbindet, könnte auf anderen Websern zu Problemen führen. Am besten testet ihr es einfach. Thema Sicherheit: Wie Kenny in den Kommentaren korrekt angemerkt hat, müssen noch Anpassungen erfolgen, wenn das Skript als tatsächliche Backup-Lösung zum Einsatz kommen soll.
Die Sicherungen landen bei diesem Skript direkt in dem Skript-Ordner. Die Sicherung(en) sollte(n) nach der Sicherung an einen anderen Ort, beispielsweise auf einen anderen Server oder ein NAS, kopiert werden. Oder ihr automatisiert den Download der Sicherungen nach dem Backupprozess.
Sowohl das Skript als auch die Sicherungen können ohne weitere Maßnahmen direkt angesprochen werden. Ihr solltet natürlich den Backup Ordner beispielsweise durch eine htpasswd absichern und den Zugriff auf die Backups über die htaccess einschränken.
Ich werde dieses Online-Backup Thema in Zukunft vermutlich nochmal aufgreifen, erweitern und optimieren. Ich verlinke das dann hier.