Seite 1 von 1

pty -- Pseudoterminals

Verfasst: Donnerstag 23. Februar 2006, 14:08
von Rebecca
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.

Verfasst: Donnerstag 23. Februar 2006, 15:49
von Joghurt
Ö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.

Verfasst: Donnerstag 23. Februar 2006, 16:53
von Rebecca
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?

Verfasst: Donnerstag 23. Februar 2006, 17:02
von Joghurt
Wie wäre es, wenn du ssh direkt mit execl aufruft?

Verfasst: Donnerstag 23. Februar 2006, 17:05
von Rebecca
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.

Verfasst: Donnerstag 23. Februar 2006, 18:19
von Joghurt
Nein, ssh in der pseudotty starten und dann das Kennwort übermitteln...

Aber du könntest wohl auch einfach popen nehmen.

Verfasst: Donnerstag 23. Februar 2006, 19:00
von Rebecca
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...

Verfasst: Donnerstag 23. Februar 2006, 21:15
von Rebecca
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: )

Verfasst: Donnerstag 23. Februar 2006, 21:25
von Rebecca
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

Verfasst: Dienstag 28. Februar 2006, 10:58
von Rebecca
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...

Verfasst: Mittwoch 5. April 2006, 08:23
von Rebecca
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.

Verfasst: Mittwoch 5. April 2006, 09:53
von Joghurt
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

Verfasst: Montag 19. Juni 2006, 16:23
von Rebecca
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.