2023/JUL/15
更新履歴
日付
変更内容 2023/JUL/09
新規作成 バージョン 2 追加 2023/JUL/15
バージョン 3 追加
本業が忙し過ぎて kon_page の更新が途絶えて久しく。
ですが、ふと思い出したように、ツールを作ってみました。
端末を操作しているとパスワードの入力を多々求められます。
公開鍵をちゃんと登録すればそれで問題無いのかもですが、、、
ちょっとしたツールを自作して試してみました。
python の pty パッケージの spawn() を使って、端末の画面出力とキー入力を見張ります。
" passwd: " という文字列に反応して、そのときの画面の行と、キー入力した文字を登録します。
次回から同じ画面の行が現れて、1.5秒キー入力が無いと、登録されている文字列をキー入力します。
登録された内容はホームディレクトリに .ezpass というファイルに記録されます。
キー入力は、python の Xlib パッケージを使って、Xのキーイベントを発行するようにしています。
せっかく pty 使っているので、pty でスマートに何とかしたかったのですが、 いまいちよく解らず、Xserverに頼ってしまいました。
便利ですがセキュリティのへったくりもないので超危険です。
自己責任で使ってください。
とりあえず Ubuntu 18.04 でしか試せていません。
kon_ut を適当に使っています。
kon_pageのpythonモジュールのインストール などご参考に。
キー入力でXlibを使っているので、python-xlib をインストールして使っています。
$ sudo apt install python-xlib
ezpass.py
#!/usr/bin/env python3
import sys
import os
import pty
import threading
import empty
import thr
import cmd_ut
import yaml_ut
import dbg
sec = 1.5
def b_to_s( b ):
try:
s = b.decode()
except:
return None
return s
def key_ev( s ):
DIR = os.path.dirname( __file__ )
cmd = 'echo {} | {}/key_ev.py'.format( s, DIR )
cmd_ut.call( cmd )
def dic_new():
d = {}
path = os.path.expandvars( "$HOME/.ezpass" )
if os.path.exists( path ):
d = yaml_ut.load_fn( path, {} )
def save():
yaml_ut.save( d, path )
def get( k ):
return d.get( k )
def put( k, v ):
if k in d:
return
d[ k ] = v
save()
return empty.new( locals() )
dic = dic_new()
def buf_new():
e = empty.new()
e.bf = ''
e.tmrs = []
def put( s ):
if s.endswith( '\n' ):
e.bf = ''
return
e.bf += s
s = dic.get( e.bf )
if s:
tmr = threading.Timer( sec, key_ev, ( s, ) )
tmr.start()
e.tmrs.append( tmr )
def key_in( s ):
if e.bf.endswith( ' password: ' ):
dic.put( e.bf, s )
def tmr_cancel():
while e.tmrs:
tmr = e.tmrs.pop( 0 )
tmr.cancel()
def read( fd ):
b = os.read( fd, 1024 )
s = b_to_s( b )
if s:
put( s )
return b
return empty.add( e, locals() )
buf = buf_new()
def ibuf_new():
e = empty.new()
e.bf = ''
def put( s ):
if s == '\r':
buf.key_in( e.bf )
e.bf = ''
return
e.bf += s
def read( fd ):
b = os.read( fd, 1024 )
buf.tmr_cancel()
s = b_to_s( b )
if s:
put( s )
return b
return empty.add( e, locals() )
ibuf = ibuf_new()
argv = sys.argv[ 1 : ]
if not argv:
argv = [ 'bash' ]
pty.spawn( argv, buf.read, ibuf.read )
# EOF
key_ev.py
#!/usr/bin/env python
import sys
import time
from Xlib import X, display, XK
import dbg
def run():
dpy = display.Display()
def c_to_string_lst( c ):
dic = {
' ': 'space',
'\n': 'Return',
'\t': 'Tab',
'\b': 'BackSpace',
'_': 'underscore',
}
if c in dic:
c = dic.get( c )
lst = [ c ]
if c.isupper() or c == 'underscore':
lst = [ 'Shift_L' ] + lst
return lst
def string_to_kcode( s ):
ksym = XK.string_to_keysym( s )
kcode = dpy.keysym_to_keycode( ksym )
return kcode
def fake_kcode( ev, kcode, wait_sec ):
dpy.xtest_fake_input( ev, detail=kcode )
dpy.flush()
time.sleep( wait_sec )
def press_release_kcodes( kcodes, wait_sec ):
for kcode in kcodes:
fake_kcode( X.KeyPress, kcode, wait_sec )
for kcode in kcodes[ :: -1 ]:
fake_kcode( X.KeyRelease, kcode, wait_sec )
wait_sec = 0.01
s = sys.stdin.read()
for c in s:
lst = c_to_string_lst( c )
kcodes = list( map( string_to_kcode, lst ) )
press_release_kcodes( kcodes, wait_sec )
if __name__ == "__main__":
run()
# EOF
端末を開いて
ezpass.py を実行すると内部で bash が実行されます。
あとは、ezpass.py が画面とキー入力を見張ります。
" password: " の文字列出力をトリガに動作します。
1.5 秒待つと、登録されているパスワードをキー入力します。
1.5 秒以内に手動で何かキー入力すると、自動入力はキャンセルされます。
ezpass.py のコマンドライン引数として、bashの変わりに実行したいコマンドを指定可能です。
個人的にはいつもscreenコマンドで複数の画面を切り替えて作業しているので、
$ ./ezpass.py screen -x
などと使ってみたいですね。
(まだ試せていません...)
~/.ezpass として YAML 形式で記録されます。
$ ./ezpass.py # 内部で bash が実行されます kondoh@autoware-ThinkPad:~/kon_page/ezpass$ kondoh@autoware-ThinkPad:~/kon_page/ezpass$ ssh localhost kondoh@localhost's password: # ここでパスワードを入力 # まずは普通にログインします Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.4.0-148-generic x86_64) : # 一旦、ログアウトします。 kondoh@autoware-ThinkPad:~$ exit ログアウト Connection to localhost closed. kondoh@autoware-ThinkPad:~/kon_page/ezpass$ # そしてもう一度 kondoh@autoware-ThinkPad:~/kon_page/ezpass$ ssh localhost kondoh@localhost's password: # ここで1.5秒待つと # ezpass.py が先程入力したパスワードを代わりに入力してくれてログイン出来ます Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.4.0-148-generic x86_64) : # ログアウトします。 kondoh@autoware-ThinkPad:~/kon_page/ezpass$ exit ログアウト Connection to localhost closed. kondoh@autoware-ThinkPad:~/kon_page/ezpass$ exit # さらに exit すると、内部で起動している bash が終了し、ezpass.py が終了します。 $ # ホームディレクトリに .ezpass が生成されてパスワードが記録されています。 $ cat ~/.ezpass 'kondoh@localhost''s password: ': xxxxx # 取扱注意です。くれぐれも自己責任で試してください。
Xlibのキーイベントの入力をやめてptyでなんとかしてみました。
その代わり、1.5秒待って、入力なければ勝手に入力してくれるというv1の動作は無理で、 パスワードのキー入力のきっかけが必要で、仕様を変更してます。
パスワードの入力が必要な場面で、タブキーの入力で登録しているパスワードが入力されます。
まだまだ Ubuntu 18.04 でしか試せていません。
v1 と同じく kon_ut を適当に使っています。
kon_pageのpythonモジュールのインストール などご参考に。
キー入力でXlibを使っているので、python-xlib をインストールして使っています。
python-xlib は不要です。
v2/ezpass.py
#!/usr/bin/env python3
import sys
import os
import pty
import empty
import yaml_ut
import dbg
sec = 1.5
def b_to_s( b ):
try:
s = b.decode()
except:
return None
return s
def dic_new():
d = {}
path = os.path.expandvars( "$HOME/.ezpass" )
if os.path.exists( path ):
d = yaml_ut.load_fn( path, {} )
def save():
yaml_ut.save( d, path )
def get( k ):
return d.get( k )
def put( k, v ):
if k in d:
return
d[ k ] = v
save()
return empty.new( locals() )
dic = dic_new()
def buf_new():
e = empty.new()
e.bf = ''
e.tmrs = []
def put( s ):
if s.endswith( '\n' ):
e.bf = ''
return
e.bf += s
def key_in( s ):
if e.bf.endswith( ' password: ' ):
dic.put( e.bf, s )
def get():
return e.bf
def read( fd ):
b = os.read( fd, 1024 )
s = b_to_s( b )
if s:
put( s )
return b
return empty.add( e, locals() )
buf = buf_new()
def ibuf_new():
e = empty.new()
e.bf = ''
def put( s ):
if s == '\r':
buf.key_in( e.bf )
e.bf = ''
return
e.bf += s
if e.bf == '\t':
return dic.get( buf.get() )
return None
def read( fd ):
b = os.read( fd, 1024 )
s = b_to_s( b )
if s:
s = put( s )
if s:
s += '\r'
b = s.encode()
return b
return empty.add( e, locals() )
ibuf = ibuf_new()
argv = sys.argv[ 1 : ]
if not argv:
argv = [ 'bash' ]
pty.spawn( argv, buf.read, ibuf.read )
# EOF
v1 と同じです。
ezpass.py のコマンドライン引数として、bashの変わりに実行したいコマンドを指定可能です。
個人的にはいつもscreenコマンドで複数の画面を切り替えて作業しているので、
$ ./ezpass.py screen -x
などと使ってみたいですね。
(まだ試せていません...)
v1 と同じです。
~/.ezpass として YAML 形式で記録されます。
端末を開いて
ezpass.py を実行すると内部で bash が実行されます。
あとは、ezpass.py が画面とキー入力を見張ります。
$ ./ezpass.py # 内部で bash が実行されます kondoh@autoware-ThinkPad:~/kon_page/ezpass$ kondoh@autoware-ThinkPad:~/kon_page/ezpass$ ssh localhost kondoh@localhost's password: # ここでパスワードを入力 # まずは普通にログインします Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.4.0-148-generic x86_64) : # 一旦、ログアウトします。 kondoh@autoware-ThinkPad:~$ exit ログアウト Connection to localhost closed. kondoh@autoware-ThinkPad:~/kon_page/ezpass$ # そしてもう一度 kondoh@autoware-ThinkPad:~/kon_page/ezpass$ ssh localhost kondoh@localhost's password: # ここでタブキーを入力すると # ezpass.py が先程入力したパスワードを代わりに入力してくれてログイン出来ます Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.4.0-148-generic x86_64) : # ログアウトします。 kondoh@autoware-ThinkPad:~/kon_page/ezpass$ exit ログアウト Connection to localhost closed. kondoh@autoware-ThinkPad:~/kon_page/ezpass$ exit # さらに exit すると、内部で起動している bash が終了し、ezpass.py が終了します。 $ # ホームディレクトリに .ezpass が生成されてパスワードが記録されています。 $ cat ~/.ezpass 'kondoh@localhost''s password: ': xxxxx # 取扱注意です。くれぐれも自己責任で試してください。
実行すると、端末のサイズが変わってしまう事に気づきました。
いつも使っているエディタ emacs -nw を実行すると、 端末の中に小さい範囲い emacs の世界が ...
適当に ioctl で端末のサイズを取得して、 初回の pty fd の操作時に、そのサイズを設定するようにしてみました。
あと、手持ちの古いMac bookで試してみると、パスワードを促すメッセージが、 "Password:" だったりして反応せず。
def key_in( s ): if e.bf.endswith( ' password: ' ): dic.put( e.bf, s )
とソースに埋め込んで固定だったので、 起動時のコマンドライン引数 -pw_msg="xxxx" で、この文字列を追加できるようにしてみました。
v3/ezpass.py
#!/usr/bin/env python3
import sys
import os
import pty
import termios
import struct
import fcntl
import empty
import yaml_ut
import dbg
sec = 1.5
argv = sys.argv[ 1 : ]
pw_msgs = [ ' password: ' ]
k = '-pw_msg='
for i in range( len( argv ) ):
if argv[ i ].startswith( k ):
s = argv.pop( i )
pw_msgs.append( s[ len( k ) : ] )
def b_to_s( b ):
try:
s = b.decode()
except:
return None
return s
def dic_new():
d = {}
path = os.path.expandvars( "$HOME/.ezpass" )
if os.path.exists( path ):
d = yaml_ut.load_fn( path, {} )
def save():
yaml_ut.save( d, path )
def get( k ):
return d.get( k )
def put( k, v ):
if k in d:
return
d[ k ] = v
save()
return empty.new( locals() )
dic = dic_new()
def get_wsz( fd ):
pfmt = 'HHHH'
wsz = struct.pack( pfmt, 0, 0, 0, 0 )
wsz = fcntl.ioctl( fd, termios.TIOCGWINSZ, wsz )
( rows, cols, xpix, ypix ) = struct.unpack( pfmt, wsz )
return ( cols, rows )
def set_wsz( fd, cols, rows ):
pfmt = 'HHHH'
wsz = struct.pack( pfmt, rows, cols, 0, 0 )
fcntl.ioctl( fd, termios.TIOCSWINSZ, wsz )
def buf_new():
e = empty.new()
e.bf = ''
e.wsz=None
def put( s ):
if s.endswith( '\n' ):
e.bf = ''
return
e.bf += s
def key_in( s ):
for pw_msg in pw_msgs:
if e.bf.endswith( pw_msg ):
dic.put( e.bf, s )
break
def get():
return e.bf
def read( fd ):
if e.wsz is None:
set_sz( fd )
b = os.read( fd, 1024 )
s = b_to_s( b )
if s:
put( s )
return b
def set_sz( fd ):
( cols, rows ) = e.wsz = get_wsz( sys.stdout.fileno() )
set_wsz( fd, cols, rows )
return empty.add( e, locals() )
buf = buf_new()
def ibuf_new():
e = empty.new()
e.bf = ''
def put( s ):
if s == '\r':
buf.key_in( e.bf )
e.bf = ''
return
e.bf += s
if e.bf == '\t':
return dic.get( buf.get() )
return None
def read( fd ):
b = os.read( fd, 1024 )
s = b_to_s( b )
if s:
s = put( s )
if s:
s += '\r'
b = s.encode()
return b
return empty.add( e, locals() )
ibuf = ibuf_new()
if not argv:
argv = [ 'bash' ]
pty.spawn( argv, buf.read, ibuf.read )
# EOF
v3.patch
diff -ur v2/ezpass.py v3/ezpass.py
--- v2/ezpass.py 2023-07-09 12:27:46.601592416 +0900
+++ v3/ezpass.py 2023-07-15 13:38:54.431241842 +0900
@@ -3,6 +3,9 @@
import sys
import os
import pty
+import termios
+import struct
+import fcntl
import empty
import yaml_ut
@@ -10,6 +13,16 @@
sec = 1.5
+argv = sys.argv[ 1 : ]
+
+pw_msgs = [ ' password: ' ]
+
+k = '-pw_msg='
+for i in range( len( argv ) ):
+ if argv[ i ].startswith( k ):
+ s = argv.pop( i )
+ pw_msgs.append( s[ len( k ) : ] )
+
def b_to_s( b ):
try:
s = b.decode()
@@ -39,10 +52,22 @@
dic = dic_new()
+def get_wsz( fd ):
+ pfmt = 'HHHH'
+ wsz = struct.pack( pfmt, 0, 0, 0, 0 )
+ wsz = fcntl.ioctl( fd, termios.TIOCGWINSZ, wsz )
+ ( rows, cols, xpix, ypix ) = struct.unpack( pfmt, wsz )
+ return ( cols, rows )
+
+def set_wsz( fd, cols, rows ):
+ pfmt = 'HHHH'
+ wsz = struct.pack( pfmt, rows, cols, 0, 0 )
+ fcntl.ioctl( fd, termios.TIOCSWINSZ, wsz )
+
def buf_new():
e = empty.new()
e.bf = ''
- e.tmrs = []
+ e.wsz=None
def put( s ):
if s.endswith( '\n' ):
@@ -52,19 +77,28 @@
e.bf += s
def key_in( s ):
- if e.bf.endswith( ' password: ' ):
- dic.put( e.bf, s )
+ for pw_msg in pw_msgs:
+ if e.bf.endswith( pw_msg ):
+ dic.put( e.bf, s )
+ break
def get():
return e.bf
def read( fd ):
+ if e.wsz is None:
+ set_sz( fd )
+
b = os.read( fd, 1024 )
s = b_to_s( b )
if s:
put( s )
return b
+ def set_sz( fd ):
+ ( cols, rows ) = e.wsz = get_wsz( sys.stdout.fileno() )
+ set_wsz( fd, cols, rows )
+
return empty.add( e, locals() )
buf = buf_new()
@@ -100,8 +134,6 @@
ibuf = ibuf_new()
-argv = sys.argv[ 1 : ]
-
if not argv:
argv = [ 'bash' ]
パスワード入力トリガとして、例えば "Password:" と "passwd: " を追加して動作させる場合は、
$ ./ezpass.py -pw_msg="Password:" -pw_msg="passwd: "
$ ./ezpass.py -pw_msg="Password:" -pw_msg="passwd: " screen -x
などと指定して起動します。