pty -- Pseudoterminals

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
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Da man an ssh, su und Konsorten Passwoerter nicht ueber stdin schicken kann und Pipes somit nicht funktionieren, habe ich mich mal an pty gewagt. Die Doku ist leider nicht sehr aufschlussreich, google auch nicht, nun habe mir folgendes ueberlegt:

Code: Alles auswählen

[pid, ttyfile] = pty.fork();

if pid == 0:
    os.execl("/usr/X11R6/bin/xterm", "xterm", "-e", "top");    
else:
    cmd = raw_input("Send to top: ");
    written = os.write(ttyfile, cmd);
    print "Sent:", written;
    os.waitpid(pid, 0);
Wie gewuenscht wird ein xterm-Fenster geoeffnet, in dem ein top laeuft. Dem top will ich dann ein Kommando uebergeben (z.B. '?' um die Hilfe aufzurufen oder 'q' zum Beenden) -- geht aber nicht so, wie ich das denke.

:?: Bin gerade voll verwirrt.... ich habe mir auch mal den Quellcode von pexpect angeschaut, aber ich sehe nicht, was da anders laeuft.
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Öffne kein xterm sondern eine shell, z.B. /bin/sh

Xterm lauscht nicht an stdin/stdout, es ist im Gegenteil ja genau dafür da, andere Programme auszuführen, und deren stdout auf X11 Screens zu zeichnen.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Joghurt hat geschrieben:Xterm lauscht nicht an stdin/stdout
Aber ssh z.B. lauscht fuers Passwort auch nicht an stdin, deswegen geht's ja nicht mit Pipes, und deswegen benutze ich pty...

Naja, ich habe dann mal folgendes probiert, geht aber auch nicht:

Code: Alles auswählen

[pid, ttyfile] = pty.fork();

if pid == 0:
    os.execl("/bin/bash", "bash");    
else:
    os.write(ttyfile, "exit" + os.linesep);    
    os.waitpid(pid, 0);
Der exit-Befehl kommt anscheinend nicht an, das Programm bleibt bei waitpid stecken.

Aus der Dokumentation zu pty.fork():
Connect the child's controlling terminal to a pseudo-terminal.
Muss ich das dem execl noch irgendwie klarmachen? Wenn ja, wie?
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Wie wäre es, wenn du ssh direkt mit execl aufruft?
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Aber wie soll ich dann das Passwort uebermitteln? Es gibt dafuer keine Kommandozeilenoption oder so.

Da bliebe nur die Moeglichkeit ueber RSA-Keys, aber das moechte ich nicht.
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Nein, ssh in der pseudotty starten und dann das Kennwort übermitteln...

Aber du könntest wohl auch einfach popen nehmen.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Joghurt hat geschrieben:Nein, ssh in der pseudotty starten und dann das Kennwort übermitteln...
:?: Das ist ja mein Ziel. Ich nehme die bash doch erstmal nur zum Probieren. Wenn ich an die bash nix gesendet bekomme, dann doch an ssh wohl auch nicht. Oder hab ich dich jetzt falschverstanden?

Joghurt hat geschrieben:Aber du könntest wohl auch einfach popen nehmen.

Nein, siehe dieser Thread http://www.python-forum.de/viewtopic.ph ... hlight=ssh,
der letzte Post. Eben weil ssh zur Passworteingabe nicht an stdin horcht.

Was klappt, ist vom filedescripror mittels os.read zu lesen. Dann bekomme ich den Prompt der bash zurueck. Mmh. Geht Ein- und Ausgabe ueber den gleichen Filedescriptor? :roll:
Muss ich das Ding flushen?

Ich geh nochmal was testen...
Zuletzt geändert von Rebecca am Donnerstag 8. Juni 2006, 12:52, insgesamt 1-mal geändert.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Ich komme der Sache naeher:

Code: Alles auswählen

[pid, tty] = pty.ford()

if pid == 0:
    os.execl("/bin/sh", "sh")
else:
    ttyfileO = os.fdopen(tty, "r")    
    ttyfileI = os.fdopen(tty, "w")
    ttyfileI.write("ls" + os.linesep);


EDIT: Uups... weiter im naechsten Beitrag... (Keine Ahnung, was passiert ist :oops: )
Zuletzt geändert von Rebecca am Mittwoch 1. März 2006, 15:45, insgesamt 1-mal geändert.
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Ich komme der Sache naeher:

Code: Alles auswählen

[pid, tty] = pty.ford()

if pid == 0:
    os.execl("/bin/sh", "sh")
else:
    ttyfileO = os.fdopen(tty, "r")    
    ttyfileI = os.fdopen(tty, "w")
    ttyfileI.write("ls" + os.linesep);
    ttyfileI.flush();

    print ttyfileO.read(40);
    os.waitpid(pid, 0);
Jetzt kann ich tatsaechlich die Ausgabe von ls sehen... jedenfalls die ersten 40 zeichen. Dumm ist nur, dass read solange wartet, bis 40 Zeichen da sind (oder vermutlich EOF). Einfach nur read() wartet also bis in die Ewigkeit.

Und das Kommando exit funktioniert nur, wenn man danach nochmal liest.

Uebrigens funktioniert pty.fork genauso wie die glib-Funktion forkpty. Das hat mir beim googlen etwas geholfen. :D
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

So, nochmal der Vollstaendigkeit halber scp via Python (Achtung, mit null Fehlerbehandlung! :o ):

Erstmal zwei Funktionen zum lesen:

Code: Alles auswählen

#read a line:
def readline(tty):
    string = "";
    while True:
        c =  os.read(tty, 1);    
        if c == os.linesep: return string;
        string += c;


#read until exp_string has been read:
def expect(tty, exp_string):
    string = "";
    while True:
        string +=  os.read(tty, 1);
        if exp_string in string:  return string;
Nun das eigenliche scp:

Code: Alles auswählen

#copy a file to a remote host:
def rcopy(host, user, passwd, lfile, rfile):
    
    print lfile, user + "@" + host + ":" + rfile;
    [pid, tty] = pty.fork();

    if pid == 0:
        full_rfile = user + "@" + host + ":" + rfile;
        os.execl("/usr/bin/scp", "scp", lfile, full_rfile);
        
    else:
        print expect(tty, " password: ");   #wait for password prompt
        os.write(tty, passwd + os.linesep); #provide password
        print readline(tty);    #without this the write has no effect
        print readline(tty);    #... whereas this isn't necessary
        
        os.waitpid(pid, 0);
Wobei, wie gesagt, ein write ohne ein anschliessendes read keinen Effekt hat.

Wenn man lesen moechte, aber nicht genau weiss, ob es noch Zeichen zum lesen gibt, ist das oben benutzte os.read(fd, n) nicht sinnvoll, denn es blockiert solange, bis mindestens ein Zeichen da ist (wartet aber nicht, bis alle n Zeichen da sind). --> select.select(...) benuzten? (da kann man timeout angeben.). Wird halt ein bisschen komplizierter, wenn man nicht genau weiss, was fuer eine Ausgabe das Programm produziert (z.B. wenn man ein ls an eine Shell absetzt).

Mit dem folgenden kann man uebrigens das echo der Konsole ein- oder ausschalten:

Code: Alles auswählen

    new = termios.tcgetattr(tty);
    new[3] = new[3] & ~termios.ECHO;         #echo off
    #new[3] = new[3] | termios.ECHO;           #echo on, this is the default
    termios.tcsetattr(tty, termios.TCSANOW, new);
wobei der Effekt trotz TCSANOW nicht augenblicklich eintritt, sondern erst nach einem klitzekleinen Augeblick.

EDIT: Typos gefunden...
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Mmh, warum liefert die bash, ueber ssh und pty aufgerufen, eigentlich '\r\n' als Zeilenumbruch?

Code: Alles auswählen

#! /usr/bin/env python

import pty;
import os;
import select;
import popen2;

PROMPTS = ["> ", "# ", "$ "];

######################################################################
def expect(tty, exp_strings, timeout):
    string = "";
    while True:
        [read, write, error] = select.select([tty], [], [], timeout);
        if tty in read:
            string += os.read(tty, 1);
            for exp_string in exp_strings:
                if exp_string in string:
                    return string;
        else:
            raise TimeOutError();
       

######################################################################
def rlogin(host, user, passwd, timeout):

    PASSWDS = ["password: ", "Password: "];
   
    [pid, tty] = pty.fork();

    if pid == 0:
        os.execl("/usr/bin/ssh", "ssh", user + "@" + host);
       
    else:
        #Wait for password prompt:
        string = expect(tty, PASSWDS, timeout);
                   
        #provide password:
        os.write(tty, passwd + os.linesep);

        #Wait for shell prompt:
        string = expect(tty, PROMPTS, timeout);
       
        return (pid, tty);


######################################################################
# main part:

(pid, tty) = rlogin("localhost", "rbreu", "****", 10);

print "Erwarteter Zeilenumbruch:", repr(os.linesep)

#provide command:
os.write(tty, "echo Hallo\\ Welt\!" + os.linesep);
#read echo of command:
print "pty-ssh:", repr(expect(tty, [os.linesep], 10));
#read result of command:
print "pty-ssh:", repr(expect(tty, [os.linesep], 10));

#same with popen:
(stdout, stdin) = popen2.popen2("echo Hallo\\ Welt\!");
print "popen2:", repr(stdout.read());  
Das liefert:

Code: Alles auswählen

Erwarteter Zeilenumbruch: '\n'
pty-ssh: 'echo Hallo\\ Welt\\!\r\n'
pty-ssh: 'Hallo Welt!\r\n'
popen2: 'Hallo Welt!\n'
Wobei interessanterweise das '\n', welches ich sende um das Kommando zu bestaetigen, auch zu '\r\n' wird.
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Rebecca hat geschrieben:

Code: Alles auswählen

def expect(tty, exp_strings, timeout):
Da fehlt ein

Code: Alles auswählen

if (tty, exp_strings, timeout) == ("The", "Spanish", "Inquisition"):
  raise ValueError("NOBODY expects the Spanish Inquisition!")
SCNR
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Und wieder Neuigkeiten vom Peudoterminal. :D Habe mich gerade nochmal damit beschaeftigt, wie read auf einem pty in verschiedenen Situationen reagiert:

Ist das pty mit einen Prozess verknuepft:
  • OK, wenn mehr Zeichen vorhanden sind als gelesen werden sollen
  • Blockiert, wenn weniger Zeichen vorhanden sind als gelesen werden sollen (solange bis genug Zeichen da sind)
Ist das pty mit keinen Prozess mehr verknuepft: (Prozess wurde z.B. schon beendet)
  • OK, wenn noch mind. ein Zeichen vorhanden ist (egal, ob mehr Zeichen als vorhanden gelesen werden sollen oder nicht! Es wird gelesen, was da ist.)
  • IOError, wenn zu Beginn des Vorgangs kein Zeichen zum Lesen vorhanden ist.
Antworten