change_owner.py

Standardmäßig werden bei all-inkl.com PHP-Scripte per mod_php und nicht per fast-cgi ausgeführt. Wird innerhalb eines per mod_php ausgeführten Scriptes eine Dateien oder Verzeichnisse angelegt, so gehören diese Daten dem User und der Gruppe www-data.  Dummerweise kann dann aber der FTP-User ggf. nicht mehr auf richtig auf die Dateien zugreifen, sodass die Dateien per SSH oder FTP nicht mehr abgeändert werden können.

Soll auf diese Dateien später per FTP zugegriffen werden (z.B. weil die Dateien gelöscht werden sollen), kann der Besitzer der Dateien im KAS-Frontend auf den FTP-User gesetzt werden. Die Änderungen werden dann in der Regel innerhalb der nächsten Minute umgesetzt und anschließend kann per FTP-Client oder per SSH gelöscht und beliebig geändert werden. Eine Änderung per chown (z.B. per SSH) ist leider nicht möglich. Das ganze Prozedere per KAS ist allerdings etwas umständlich; vor allem weil immer wieder zum Browser gewechselt muss, wenn eigentlich nur schnell eine Konfigurationsänderung per SSH durchgeführt werden soll.

Daher habe ich mir ein Python-Script geschrieben (change_owner.py), dass das Prozedere zumindest für meinen Workflow etwas vereinfacht.

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# usage: change_owner.py [-h] [-d DIRECTORY] [-l LOGIN] [-o OWNER] [-p PASSWORD]
#                        [-v]
#
# Den Owner eines Verzeichnisses mithilfe der KAS-Funktion 'Besitzrechte'
# ändern.
#
# optional arguments:
#  -h, --help            show this help message and exit
#  -d DIRECTORY, --directory DIRECTORY
#                        Das Verzeichnis für das der Besitzer gewechstel
#                        werden soll.
#  -l LOGIN, --login LOGIN
#                        Der Domain-Name auf dem das Verzeichnis gewechselt
#                        werden soll. Der Domain-NAme wird ohne subdomain
#                        angegeben und ist gleichzeitig der Login-Name für das
#                        KAS (Default: Wert wird vom Script erfragt).
#  -o OWNER, --owner OWNER
#                        Der neue Besitzer des Verzeichnisses.
#  -p PASSWORD, --password PASSWORD
#                        Passwort für den KAS-Zugang bei all-inkl (Default:
#                        Passwort wird erfragt).
#  -v, --verbose         Ausführliche Ausgaben aktivieren.
#
# Historie:
#               2015.08.03: Erste Version erstellt
#
# (c) 2015 klein-gedruckt.de
#

# Python Imports
import re, sys, os
import requests
import logging
import getpass
import argparse

# Variablen definieren:
BASE_URL = "https://kas.all-inkl.com/"
act_path = "/"
up_dir = {}

# Warnmeldungen von modul Requests in separates Log umleiten
# Anmerkung: Besser wäre Python >= 2.7.9
logging.captureWarnings(True)

# Umlaute verfügbar machen um UnicodeDecodeError
# zu behebem
reload(sys)
sys.setdefaultencoding('utf8')

###############################################
#
# Funktionen
#
def getDirs(dictDir):
        global act_path
        act_path = dictDir['dir']

        payload = {     'abfrage': dictDir['dir'], 'login': dictDir['login'], 'auth': dictDir['auth'], 'server': dictDir['server'],
                                'feldname': 'verzeichnis', 'mode': 'dir', 'startdir': '', 'boxid': 'vorschlagsbox', 'autolisteid': 'auto_liste_vorschlaege' }
        r = requests.post(BASE_URL + '/inc/ajax_ftp_autopfad.php', data=payload)
        if not (r.status_code == requests.codes.ok):
                print '[*] Fehler beim Abruf der Tools-Seite.'
                exit(1)
        p = re.compile( r"^.*vorschlag_suchen\('([^']*)','([^']*)','([^']*)','([^']*)'.*$", re.MULTILINE )
        matches = p.findall( r.text )

        dirs = {}
        id = 1
        for line in matches:
                if not (line[0] == '/./'):
                        dirs.update( { id: { 'dir': line[0], 'login': line[1], 'auth': line[2], 'server': line[3] } })
                        id = id + 1

        return dirs

def showDirs(local_dirs):
        global act_path

        print
        print "[*] Verzeichnis auswählen:"
        print
        if not (act_path == "/"):
                print "    [0] .."
        for id in local_dirs.keys():
                print "    [" + str(id) + "] " + local_dirs[id]['dir']
        print
        print "    [x] Aktuellen Pfad auswählen: " + act_path
        print
        choice = ""
        if not (act_path == "/"):
                ranger = [str(x) for x in range(0, len(local_dirs) + 1)]
        else:
                ranger = [str(x) for x in range(1, len(local_dirs) + 1)]
        choice = raw_input("  [##] wechselt in Verzeichnis,  [x] wählt Verzeichnis aus: ")
        while (choice not in ranger) and  not (choice == "x") and not (choice == 'X'):
                choice = raw_input("  [##] wechselt in Verzeichnis,  [x] wählt Verzeichnis aus: ")

        if (choice == 'x') or (choice =='X'):
                return { 'path': act_path, 'dirs': '' }
        elif (choice == '0'):
                up_dir['dir'], tail = os.path.split(act_path)
                if (tail == ""):
                        up_dir['dir'], tail = os.path.split(up_dir['dir'])
                return { 'path': '', 'dirs': getDirs(up_dir) }
        else:
                return { 'path': '', 'dirs': getDirs(local_dirs[int(choice)]) }


# Parameter definieren und parsen
parser = argparse.ArgumentParser(description="Den Owner eines Verzeichnisses mithilfe der KAS-Funktion 'Besitzrechte' ändern.")
parser.add_argument('-d', '--directory', help='Das Verzeichnis für das der Besitzer gewechstel werden soll.')
parser.add_argument('-l', '--login', help='Der Domain-Name auf dem das Verzeichnis gewechselt werden soll. Der Domain-NAme wird ohne subdomain angegeben und ist gleichzeitig der Login-Name für das KAS (Default: Wert wird vom Script erfragt).')
parser.add_argument('-o', '--owner', help='Der neue Besitzer des Verzeichnisses.')
parser.add_argument('-p', '--password', help='Passwort für den KAS-Zugang bei all-inkl (Default: Passwort wird erfragt).')
parser.add_argument('-v', '--verbose', action="store_true", help='Ausführliche Ausgaben aktivieren.')
args = parser.parse_args()

if not args.login:
        args.login = raw_input('Domain und Username für KAS: ')

if not args.password:
        args.password = getpass.getpass('Passwort für KAS: ')

# 1. Schritt: Anmeldung am Portal
payload = {'loginname': args.login, 'passwort': args.password, 'language': 'deutsch', 'sslcheckbox': 'on'}
r = requests.post(BASE_URL, data=payload)
if not (r.status_code == requests.codes.ok):
        print '[*] Fehler beim KAS-Login. Credentials korrekt?'
        exit(1)

# 2. Schritt: Tools-Link aus der Antwort extrahieren
p = re.compile( r'^.*href="(.*s=.*&a=[^"]*)\".*Tools.*$', re.MULTILINE )
m = p.search( r.text )
if m:
        if args.verbose:
                print '[*] Tools-Link extrahiert.'
        next_url = m.group(1)
        if args.verbose: print '    [*] Extrahierte URL: ' + next_url
else:
        print '[*] Kein Tools-Link gefunden. Sind die Credentials korrekt?'
        exit(1)

# 3. Schritt: Tools-Link aufrufen
r = requests.get(BASE_URL + next_url)
if not (r.status_code == requests.codes.ok):
        print '[*] Fehler beim Abruf der Tools-Seite.'
        exit(1)

# 4. Schritt: Besitzrechte-Link extrahieren
p = re.compile( r'^.*href="(.*s=.*&a=[^"]*)\".*Besitzrechte.*$', re.MULTILINE )
m = p.search( r.text )
if m:
        if args.verbose:
                print '[*] Besitzrechte-Link extrahiert.'
        next_url = m.group(1)
        if args.verbose: print '    [*] Extrahierte URL: ' + next_url
else:
        print '[*] Kein Besitzrechte-Link gefunden.'
        exit(1)

# 5. Schritt: Besitzrechte-Link abrufen
formular = requests.get(BASE_URL + next_url)
if not (formular.status_code == requests.codes.ok):
        print '[*] Fehler beim Abruf der Tools-Seite.'
        exit(1)

# 6. Schritt: a extrahieren
p = re.compile( r'^.*input.*style=.*name="a" value="([^"]*)".*$', re.MULTILINE )
m = p.search( formular.text )
if m:
        a = m.group(1)
        if args.verbose:
                print '[*] Wert für a extrahiert (a=' + a + ').'
else:
        print '[*] Fehler beim Abruf der a-Tokens.'
        exit(1)

# 7. Schritt: s extrahieren
p = re.compile( r'^.*input.*style=.*name="s" value="([^"]*)".*$', re.MULTILINE )
m = p.search( formular.text )
if m:
        s = m.group(1)
        if args.verbose:
                print '[*] Wert für s extrahiert (s=' + s +').'
else:
        print '[*] Fehler beim Abruf der s-Tokens.'
        exit(1)

# 8. Schritt: Verzeichnis prüfen und auswählen
if not args.directory:
        # / auslesen
        p = re.compile( r"^.*vorschlag_suchen\('([^']*)','([^']*)','([^']*)','([^']*)'.*$", re.MULTILINE )
        matches = p.findall( formular.text )

        if (matches[0][0]) and (matches[0][1]) and (matches[0][2]) and (matches[0][3]):
                up_dir = { 'dir': matches[0][0], 'login': matches[0][1], 'auth': matches[0][2], 'server': matches[0][3] }
                dirs = getDirs(up_dir)
        else:
                print '[*] Fehler beim Abruf der Tokens zum Abruf des Verzeichnis "/".'
                exit(1)

        path = ''
        while path == '' :
                buf = showDirs(dirs)
                path = buf['path']
                dirs = buf['dirs']

else:
        print "[*] Ändere Verzeichnis " + args.directory + "."
        print "    Hinweis: Es wird nicht geprüft ob das Verzeichnis existiert."


# 9. Schritt: User extrahieren
print
p = re.compile( r'^.*option.*value="([^"]*)".*$', re.MULTILINE )
users = p.findall( formular.text )
if users:
        if (not args.owner) or ( not (args.owner in users)):
                if (args.owner) and (not (args.owner in users)): print '[*] Falscher Benutzername angegeben.'
                print '[*] Bitte gültigen Benutzer auswählen:'
                print
                i = 1
                for user in users:
                        print '    [',i,'] ' + user
                        i = i + 1
                print
                choice = ""
                ranger = [str(x) for x in range(1, len(users) + 1)]
                choice = raw_input("  Neuen Owner wählen (" + str(ranger) + "): ")
                while choice not in ranger:
                        choice = raw_input("  Neuen Owner wählen (" + str(ranger) + "): ")
                choice = int(choice) - 1
                args.owner = users[choice]
else:
        print '[*] Fehler beim Abruf der User.'
        exit(1)

# 10. Schritt: Berechtigungen setzen
payload = {'user': args.owner, 'verzeichnis': path, 'rekursiv': 'rekursiv', 'a': a, 's': s, 'button1': 'Besitzrechte+jetzt+setzen'}
r = requests.post(BASE_URL + "/index.php", data=payload)

p = re.compile( r"^.*<p class='success'>Die Verzeichnisrechte.* Dies kann einige Minuten dauern\..*$", re.MULTILINE )
m = p.search( r.text )

if m:
        print
        print "[*] ERFOLG: Die Verzeichnisrechte werden zurück gesetzt. Es kann einige"
        print "            Minuten dauern, bis die Änderungen wirksam werden."
else:
        print "[*] Fehler beim Zurücksetzen der Verzeichnisrechte."
        exit (1)

Das Script kann z.B. auch auf dem all-inkl-Server abgelegt werden, so dass es direkt in einer SSH-Sitzung aufgerufen werden kann, wenn man es braucht. Dazu muss lediglich das Python-Modul auf dem Server eingerichtet werden. Wie das funktioniert ist dann Thema für einen eigenen Blog-Beitrag.