2017-03-13

sshrcコードリーディング

sshrcのコードリーディングをしてみました。

sshrcとは?

sshでリモートログインをするときの初期化処理を行うことができる便利ツール(sshのラッパ関数)です。

sshrcを使うと複数のサーバにリモートログインする場合、リモートログイン先で設定関連のスクリプトを複製しなくても良くなります。また、sshrcでリモートログインしたときに生成される一時ファイルはログアウト後に削除されるため環境を汚しません。

使い方

ホームディレクトリに.sshrcファイルを設置してsshrcコマンドでリモートログインすればOKです。

また.sshrc.dディレクトリを作成してその中にvimrcなどの設定ファイルを置くことができます。.sshrc.dディレクトリはリモートログイン先にコピーが作成されるので、これらの設定ファイルをリモートでも利用できるようになります。

コードリーディング

sshrc_parse関数ではsshrcの引数をパースしてsshrc関数に引数を適切に渡します。
function sshrc_parse() {
  while [[ -n $1 ]]; do
    case $1 in
      -b | -c | -D | -E | -e | -F | -I | -i | -L | -l | -m | -O | -o | -p | -Q | -R | -S | -W | -w )
        SSHARGS="$SSHARGS $1 $2"; shift ;;
      -* )
        SSHARGS="$SSHARGS $1" ;;
      *)
        if [ -z "$DOMAIN" ]; then
         DOMAIN="$1"
        else
...

.sshrcは作成必須です。なければエラーになります。

if [ -f $SSHHOME/.sshrc ]; then
   ...
else
    echo "No such file: $SSHHOME/.sshrc" >&2
    exit 1
fi

また設定ファイル(.sshrcと.sshrc.dディレクトリ配下のファイル)の総量が64KBを超えるとエラーになります。

SIZE=$(tar cfz - -h -C $SSHHOME $files | wc -c)
if [ $SIZE -gt 65536 ]; then
    echo >&2 $'.sshrc.d and .sshrc files must be less than 64kb\ncurrent size: '$SIZE' bytes'
    exit 1
fi

tarのcfzオプションは対象のファイルをgzipで圧縮し -hはシンボリックリンクをリンクではなく実体として圧縮するフラグです。-Cは$SSHHOMEでtarの圧縮を行うことを明記するオプションで、これらをwc -cに渡してバイト数を計算しています。

ちなみに$SSHHOMEはローカル側とリモート側各々で設定されます。デフォルトではローカルはホームディレクトリ、リモートはtmpディレクトリになります。

以下はコマンドが定義されていない、かつ、~/.sshrc.d/.hushloginが存在しない場合にwelcome messageを定義しています。

if [ -z "$CMDARG" -a ! -e ~/.sshrc.d/.hushlogin ]; then
    WELCOME_MSG="
        if [ ! -e ~/.hushlogin ]; then
            if [ -e /etc/motd ]; then cat /etc/motd; fi
            if [ -e /etc/update-motd.d ]; then run-parts /etc/update-motd.d/ 2>/dev/null; fi
            last -F \$USER 2>/dev/null | grep -v 'still logged in' | head -n1 | awk '{print \"Last login:\",\$4,\$5,\$6,\$7,\$8,\"from\",\$3;}'
        fi
    "
else
    WELCOME_MSG=""
fi

ssh -tで仮想端末を割り当ててコマンドを実行します。

ssh -t "$DOMAIN" $SSHARGS "
...

サーバ側にopensslがなかったらエラーで終了します。command -vでコマンドの存在チェックをしています。

command -v openssl >/dev/null 2>&1 || { echo ...; exit 1}

テンポラリのディレクトリを作成します

export SSHHOME=\$(mktemp -d -t .$(whoami).sshrc.XXXX)
export SSHRCCLEANUP=\$SSHHOME
trap <span class="pl-cce">\"</span>rm -rf <span class="pl-cce">\$</span>SSHRCCLEANUP; exit<span class="pl-cce">\"</span> 0

$(whoami)はsshrcを実行したシェルで評価されるので、クライアント側のユーザ名がセットされます。またtrapによってsshrcによるリモートログイン終了時(=プロセス終了時)にテンポラリのディレクトリを削除しています。

次にクライアントのファイルをbase64にしてコマンド文字列経由でリモートに送っています。以下はsshrcの実体をcatを使って送っている例です。

echo $'"$(cat "$0" | openssl enc -base64)"' | tr -s ' ' $'\n' | openssl enc -base64 -d > \$SSHHOME/sshrc

opensslのbase64はPEMコンテキストなため64文字ごとの改行区切りで出力されます。$'string'記法はエスケープシーケンスを有効にする書き方です。ダブルクォートでくくらないと改行が空白に置き換わるのでtrで改行コードに置換します。ダブルクォートを使っていないのは$'string'記法を既に使っていて併用できないからです。

sshrc.bashrcはログインシェルに食わせるrcファイルです。

echo $'"$( cat << 'EOF' | openssl enc -base64
                if [ -r /etc/profile ]; then source /etc/profile; fi
                if [ -r ~/.bash_profile ]; then source ~/.bash_profile
                elif [ -r ~/.bash_login ]; then source ~/.bash_login
                elif [ -r ~/.profile ]; then source ~/.profile
                fi
                export PATH=$PATH:$SSHHOME
                source $SSHHOME/.sshrc;
EOF
                )"' | tr -s ' ' $'\n' | openssl enc -base64 -d > \$SSHHOME/sshrc.bashrc

.sshrcや.sshrc.dディレクトリはtar.gzで固めてbase64にしてリモートに展開しています。

echo $'"$(tar czf - -h -C $SSHHOME $files | openssl enc -base64)"' |
 tr -s ' ' $'\n' | openssl enc -base64 -d | tar mxzf - -C \$SSHHOME

最後に指定したコマンドをsshrc.bashrcの末尾に書き込み、sshrc.bashrcをrcファイルとしてbashを起動します。

echo \"$CMDARG\" >> \$SSHHOME/sshrc.bashrc
bash --rcfile \$SSHHOME/sshrc.bashrc

CMDARGが設定されている場合はコマンド実行後にexitされます。

local SEMICOLON=$([[ "$@" = *[![:space:]]* ]] && echo '; ')
CMDARG="$@$SEMICOLON exit"

参考URL

このエントリーをはてなブックマークに追加