sudo をプログラムで使う際の注意点
『Wake on LAN してみる Part 3 (shutdown)』 で Linux サーバを停止する際の sudo の使い方に問題があったので まとめておくことに...
問題1
sudo のバージョンによって標準入力からパスワードを渡す方法が異なるらしい。
Ubuntu 8.04 LTS の sudo (1.6.9p10) では
echo "pass" | ssh hoge@192.168.1.1 "sudo /sbin/shutdown -h now"で問題がなかったが 最新の Ubuntu 10.04 LTS の sudo (1.7.2p1) では
sudo: no tty present and no askpass program specifiedのようなエラーになってしまう。どうやら
echo "pass" | ssh hoge@192.168.1.1 "sudo -S /sbin/shutdown -h now"のように "-S" オプションをつけて実行しないとダメらしい。
問題2
sudo に "-S" オプションをつけて実行すると プログラムから標準入力経由でパスワードを渡しても
[sudo] password for hoge: Sorry, try again. [sudo] password for hoge: Sorry, try again. [sudo] password for hoge: Sorry, try again. sudo: 3 incorrect password attemptsのように認証エラーになる。
どうやらパスワードの最後に 改行文字 "\n" をつけて
channel.setInputStream(new ByteArrayInputStream("{password}\n".getBytes()));としないとダメらしい。考えてみれば、当然だけど...
改行コードが無いと どこまでがパスワードなのか分からないからね。
無理して InputStream でパスワードを渡すより、ssh で実行するコマンド自体を
"echo {password} | sudo -S -p '' /sbin/shutdown -h now";としてしまった方が簡単で良いかもしれない。
echo なら何も考えなくても改行コードつけてくれるから...
問題3
sudo を実行すると
[sudo] password for hoge:のように パスワード要求プロンプトが標準エラーに出力されてしまう。
パスワードを要求するプロンプトは "-p" オプションで指定できるので
sudo -p '' ...のように空文字を指定すれば プロンプトの出力を消すことができます。
これらを踏まえると 最終的なコードは
import java.io.ByteArrayInputStream; import java.lang.*; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSch; // サーバ情報 def host = "192.168.1.1"; def port = 22; // ユーザ情報 def username = "hoge"; def password = "pass"; // shutdown コマンド def command = "sudo -S -p '' /sbin/shutdown -h now"; def jsch = new JSch(); def session = jsch.getSession(username, host, port); try { // SchException: UnknownHostKey を防ぐための設定 session.setConfig("StrictHostKeyChecking", "no"); session.setPassword(password); session.connect(5000); var channel = session.openChannel("exec") as ChannelExec; try { channel.setCommand(command); channel.setInputStream(new ByteArrayInputStream("{password}\n".getBytes())); channel.setOutputStream(System.out); channel.setErrStream(System.err); channel.connect(); // コマンドが終了するまで待機 while (not channel.isClosed()) { Thread.sleep(1000); } } finally { channel.disconnect(); } } finally { session.disconnect(); }のようになる。
もちろん 『Wake on LAN してみる Part 3 (shutdown)』 についても上記の内容でこっそり修正してあります。