su

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Ich habe zu Testzwecken ein Jail mit jailkit erstellt, der user "jailed" mit der uid 1002. Ziel ist es im Jail ein Programm zu kompelieren (Compiler sind alle vorhanden) und dann auszuführen, in der Shell ist das kein Problem:

Code: Alles auswählen

su jailed -c dmd -of…
Nur wie bewerkstellige ich das mit Python?

subprocess-Ansatz:

Code: Alles auswählen

In [10]: p = Popen(['su', 'jailed', '-c', 'dmd'], stdout=PIPE, stdin=PIPE, stderr=PIPE, shell=True)

In [11]: p.communicate('none')
Out[11]: ('', 'standard in must be a tty\n')
Gefunden habe ich dazu das hier: http://superuser.com/questions/119376/b ... t-be-a-tty, allerdings behagt es mir nicht meine sudoers-Datei zu bearbeiten, noch dazu mit Dingen zu füllen von denen ich keinen Ahnung habe. Ein weiterer Ansatz:

Code: Alles auswählen

In [14]: os.setuid(1002)
---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
/home/dav1d/workspaces/python/irc/irc/<ipython-input-14-126e5d825996> in <module>()
----> 1 os.setuid(1002)

OSError: [Errno 1] Operation not permitted
Naja ist auch logisch, Prozess ist nicht root und wie kann ich ein Passwort übergeben?

Ich bin leider aufgeschmissen …
Idee: statt subprocess pexpect? Weitere Idee aus dem IRC "nphg | evtl. liefert os.openpty() was brauchbares".

Also, wie wechsle UID des Python-Prozesses (am besten nur vorübergehend)?
the more they change the more they stay the same
Benutzeravatar
fecub
User
Beiträge: 24
Registriert: Freitag 14. November 2008, 16:53
Kontaktdaten:

so könntest du es aus python ausführen

Code: Alles auswählen

import subprocess, shlex

command_line = 'sudo jailed -c dmd -of'
				
args = shlex.split(command_line)
subprocess.Popen(args)
bei der sudo geschichte gäbe es 2 möglichkeiten, ich weiß nicht ob du was allgemeines oder für dich programmierst. du könntest
jailed auf no pass setzten. wenn du es ausführst wird nicht nach dem password gefragt

Du erstellst eine gruppe mit dem namen jailedgroup und editierst die /etc/sudoers mit diesen daten:

Code: Alles auswählen

# Command alias for jailed
Cmnd_Alias      JAILED = /usr/bin/jailed  # pfad für das programm angeben, habe ich als bsp. so geschrieben

# This is the group that is allowed to run jailed as root with no password prompt
%jailedgroup     ALL=(ALL) NOPASSWD: JAILED
alle user die in der gruppe jailedgroup mitglied sind werden nicht nach dem passwort gefragt. Dies müsstest du halt einmal machen.

2 möglich.

Code: Alles auswählen

sudo python_program.py
lunar

@Dav1d: Wenn man sowas wie jailkit nutzt, sollte man dann nicht auch in der Lage sein, sudo zu konfigurieren?

Ansonsten kannst Du das Python-Programm mit Root-Rechten starten, und im Python-Programm dann die EUID entsprechend der benötigten Rechte anpassen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

@fecub, den Prompt mit shlex zu parsen um ihn dann an Subprocess weiterzugeben ist mit dem Pfeil von hinten ins Auge. Subprocess nimmt eben gerade deswegen eine Liste als Parameter entgegen, um Befehle nicht erst für die Shell quoten zu müssen, etwa `"echo \"hallo welt\""`, wo man bei Suprocess einfach `["echo", "hallo welt"]` übergeben kann.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

@lunar, :evil: . Ich habe nur gehofft, dass das nicht nötig ist.

Zur "sudo"-Lösung,

Code: Alles auswählen

sudo -u jailed -s bar
ist eigentlich genau das was ich brauche (-s für stdinput, -u als user jailed), allerdings verlangt sudo dann nach meinem Passwort (was ja logisch ist), nur will ich mein Passwort nicht als plain-text in einer Python-Datei stehen haben. Ich versuche mal dem user jailed _kein_ Passwort zu geben, da der user eh nichts machen kann (hat nur Zugriff auf 3 Binaries und ohne interaktive shell + jailed env.), allerdings nicht in /etc/sudoers, da ich ja dann jede Binary extra angeben müsste, die ich im Jail hinzufüge (oder reicht es wenn ich die Jail-Shell erlaube? (Shell = jk_lsh))

Wenn ich das Python-Programm als root starte und die Rechte droppe (UID auf 1002 setze (= user jailed)), dann könnte ich es ja gleich als user jailed starten, oder verstehe ich da was falsch?

Ich versuche es des Weiteren mal mit pexpect.
the more they change the more they stay the same
lunar

@Dav1d: Sicher, ich hatte nur angenommen, dass Du irgendwann root-Zugriff benötigst.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Anderer Ansatz:

Code: Alles auswählen

import sys, os, tty, pty, signal, select, subprocess

STDIN, STDOUT = 0, 1

master, slave = pty.openpty()
p = subprocess.Popen(['su', 'jailed', '-c', 'dmd'],
    stdin = slave, stdout = slave, close_fds = True)

for s in signal.SIGINT, signal.SIGTERM:
    signal.signal(s, lambda signalnum, frame:
        os.kill(p.pid, signalnum))

try:
    mode = tty.tcgetattr(STDIN)
    tty.setraw(STDIN)
    restore = 1
except tty.error:
    restore = 0
    

while p.poll() is None:
    os.write(master, 'none\n')
    
    try:
        fds = select.select([master], [], [])[0]
    except select.error:
        pass
    else:
        os.write(STDOUT, 'Output: ' + os.read(master, 1024))


if restore:
    tty.tcsetattr(STDIN, tty.TCSAFLUSH, mode)
os.close(master)
sys.exit(p.poll())
von http://code.google.com/p/lilykde/source ... /runpty.py

Es funktioniert, allerdings taugt mir nicht, dass ich den Master mit dem Passwort ('none') zu spamme. Deshalb dachte ich, wenn Daten am Slave anliegen (select), sollte das nach dem Passwort gefragt werden, nur leider liegen nie Daten am Slave an (laut select) (Ich meine die Ausgabe "Passwort: ") Ideen warum das so ist?
the more they change the more they stay the same
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Programme wie su, ssh etc. fragen direkt das control terminal des Prozesses an, daher geht dass nicht auf diese Weise mit pty.

Warum nutzt Du nicht pexpect? Das geht auch den Weg über pty...
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Ich weiß auch nicht, pexpect ist wahrscheinlich der sinnvollste Weg, aber irgendwie interessiert mich das jetzt.

SSH kann die gewünschte Funktion auch emulieren (-t)

Code: Alles auswählen

       -t     Force  pseudo-tty  allocation.   This  can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing
              menu services.  Multiple -t options force tty allocation, even if ssh has no local tty.
naja ich habe mal weiter gespielt/geforscht:

Code: Alles auswählen

#!/usr/bin/env python

import os
import sys
import select

pid, fd = os.forkpty()

if pid == 0:
    os.execlp('su', 'su', 'jailed', '-c', 'dmd')
else:
    print os.read(fd, 1024)
    os.write(fd, 'none\n')
    
    while True:
        try:
            fds = select.select([fd], [], [])[0]
        except select.error:
            pass
        else:
            p = os.waitpid(pid, os.WNOHANG)
            print p
            if not os.WIFSTOPPED(p[1]):
                print os.read(fd, 1024)
Der Code funktioniert, nur beendet sich das Skript mit einem OSError:

Code: Alles auswählen

Traceback (most recent call last):
  File "test_pty2.py", line 18, in <module>
    c = os.read(fd, 1)
OSError: [Errno 5] Input/output error
trotz des os.WIFSTOPPED(p[1]) checks, allerdings wäre es auch kein Problem den Fehler einfach abzufangen. Was mich wundert, das entspricht doch letzten Skript nur, dass es ein Child-Prozess ist, Pipes sind auch mit dem slave verbunden (wie bei subprocess), es wird auch "in" den Master geschrieben, jedoch gibt "print os.read(fd, 1024)" mir "Passwort: " (prompt von su) aus, bei dem vorherigen Beispiel nicht. Ich bin etwas verwirrt.
the more they change the more they stay the same
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Um mal eine Lösung (oder gleich zwei) aufzuzeigen:

Code: Alles auswählen

import pexpect
s = pexpect.run('su jailed -c dmd', events={'\\wasswor\\w:' : 'none\n'})
print s

Code: Alles auswählen

child = pexpect.spawn('su', ['jailed', '-c', 'dmd'])
child.expect('\\wasswor\\w:')
child.sendline('none')
print child.readlines()
Wobei der 2. Code interessanterweise das(?) Prompt nur in Form von ' \r\n' zurückgibt und der erste Code es komplett in der Rückgabe hat.

PS: Ich bin natürlich noch an einer Antwort zum vorherigen Post interessiert ;)
the more they change the more they stay the same
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Dav1d hat geschrieben:SSH kann die gewünschte Funktion auch emulieren (-t)
Damit ist was anderes gemeint, nämlich der Fall, wo das Programm der Gegenseite zwingend ein Terminal erwartet (z.B. screen). ssh kann dann die Terminalfunktionalität emulieren.

Wo die Fehlermeldung her kommt, weiss ich nicht, Dein gezeigter Code passt nicht zur Fehlermeldung.
Dav1d hat geschrieben:Was mich wundert, das entspricht doch letzten Skript nur, dass es ein Child-Prozess ist, Pipes sind auch mit dem slave verbunden (wie bei subprocess), es wird auch "in" den Master geschrieben, jedoch gibt "print os.read(fd, 1024)" mir "Passwort: " (prompt von su) aus, bei dem vorherigen Beispiel nicht. Ich bin etwas verwirrt.
Unterschied beider Varianten ist das controlling terminal. Dein erstes Skript sollte das Passwortprompt auf dem Terminal präsentieren, von dem aus Du das Skript gestartet hast. Siehe hierzu os.forkpty

Dav1d hat geschrieben: Wobei der 2. Code interessanterweise das(?) Prompt nur in Form von ' \r\n' zurückgibt und der erste Code es komplett in der Rückgabe hat.
Willkommen in der Welt der Terminalsteuerzeichen und ANSI-Codes :) Wobei pexpect das weitestgehend unterdrückt (hat selbst einen kleinen Terminalemulator/-parser an Bord)
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Danke für die Antwort, so der richtige Traceback:

Code: Alles auswählen

Traceback (most recent call last):
  File "test_pty2.py", line 26, in <module>
    print os.read(fd, 1024)
OSError: [Errno 5] Input/output error
the more they change the more they stay the same
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Du erhältst die Ausnahme aufgrund eines Fehlers in der Programmlogik. Du rufst zwar select auf, testest aber nicht, ob fd lesbar ist (also in rlist auftaucht). Dein try/except-Konstrukt testet eigentlich nur auf Gültigkeit des Filedescriptors, ein falscher oder ungültiger Filedescriptor würde dann zu select.error führen.
Was passiert ist Folgendes:
Pseudoterminals funktionieren wie eine Pipe, nur etwas komplizierter. Dein child-Prozess beendet sich und damit ist das andere Ende der Pipe geschlossen. Da dieses das einzig offnene Slave-Ende war, führt read am Master OHNE Daten im Pipepuffer zu dem OSError. Dein Exceptionhandling kann den Fehler aber nicht abfangen, da der master-Filedescriptor prinzipiell noch gültig ist (das Slave-Ende eines Pseudoterminals darf beliebig oft geöffnet werden, dh. Du könntest mit einem anderen Prozess da rangehen und die Pipe "reaktivieren")
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Ok, danke für die Erklärung
the more they change the more they stay the same
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Hier mal eine einfache Interaktion mit einer Shell:

Code: Alles auswählen

import pty, os, time, sys
from select import poll, POLLIN

STDIN = sys.stdin.fileno()

if __name__ == '__main__':
    pid, master = pty.fork()
    if pid == 0:
        os.execl('/bin/bash', '')
    poll = poll()
    poll.register(master, POLLIN)
    poll.register(STDIN, POLLIN)
    run = True
    while run:
        time.sleep(.1)
        for fd, event in poll.poll(10):
            # pty output zu terminaloutput
            if fd == master and event & POLLIN:
                s = os.read(master, 1024)
                print 'Output:', repr(s) # output mit steuerzeichen
#                sys.stdout.write(s)
#                sys.stdout.flush()
            # terminalinput zu pty input
            if fd == STDIN and event & POLLIN:
                os.write(master, os.read(STDIN, 1024))
        try:
            os.waitpid(pid, os.WNOHANG)
        except OSError:
            run = False
Steuern kann man die Shell normal über die Eingabe, 'exit\n' beendet die Shell und das Pythonskript. Parst man die pty-Ausgabe auf bestimmte Werte und triggert entsprechende Aktionen, landet man beim expect-Ansatz. Verbessert man die Interaktion der beiden Terminals (mit Abgleich der Terminaleinstellung über termios, Einführen eigener Keycodes etc.) hat man einen screen-Klon.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Wow danke :)
the more they change the more they stay the same
Antworten