cygwin を sshクライアントとして使ってみた ( sshpass によるパスワード省略付)

最近までMacを中心に使ってたエンジニアです。

プロジェクトの都合により急遽windowsを使うことになったのですが、
最近のwindows sshクライアント事情がわからずいくつか試してみました。

・RLogin
・Poderosa
・Putty
・ZOC Terminal
...

色々接続先や設定が試せるのが良いなと思うのですが、
複数のサーバで色々管理するとなると、どれもしっくり来ない。
そして色々試す中で cygwin (mintty) で良いんじゃ？と気づき、
試してみた所、以外と良い感じなことに気づきました。

しかし、初期状態だと色々足りてないので、その際の導入メモ。


### キーマップ
　良くわからない改行が起きたら、とりあえず .exrc に以下を追加。

```.bash:.exrc
set nocompatible
set backspace=indent,eol,start
```

### プロンプト
　デフォルトのプロンプトが好きになれない人はPS1を変更も

```.bash:.bash_profile
PS1="\[\033[0;36m\]\u\[\033[0;36m\]@\h\[\033[0;37m\]:\w$\[\033[0m\] "
```

### wget, ssh, scp
　標準だと入ってない。ssh client系をインストール
　(インストーラから指定)

### apt-cyg
　足りない cygwinのモジュールを都度インストールするのは手間がかかります。
　cygwinにもyumを入れたいのですが存在しないので、apt-cyg なるものを利用。
　※ cygwinを本格的に使うには、ほぼ必須のパッケージです。
　※ 本家apt-cyg は停止中。forkされてるものをお好みで。

```
$ lynx -source rawgit.com/transcode-open/apt-cyg/master/apt-cyg > apt-cyg
$ ls -l
$ install apt-cyg /bin
```

今後他のパッケージをインストールする際に備えて以下もインストール

```
$ apt-cyg install git
$ apt-cyg install make gcc-g++ automake
```

### cocot / 文字化け
　ping 等 windowsのコマンドにて処理されてるものは、sjisの為、
　cygwinのutf8で結果を表示した際に文字化が発生。
　該当コマンドは例外で個別対応が必要となります。
　方法はiconvを噛ませる方法, cocot を使う方法といくつかありますが、
　cocotはあった方が他にも使えて便利な為、今回はcocotで。

```
$ apt-cyg install libiconv libiconv-devel inetutils
$ git clone http://github.com/vmi/cocot
$ cd cocot
$ ./configure
$ make && make install

# ※ 動作に必要な物がないと色々エラーが出ます。
# 　(例:  `aclocal-1.15: コマンドが見つかりません`  ...  automakeが必要)
#　その為、エラーが出た際は、上の apt-cyg のセクションのパッケージのインストールの確認を。
```

cocotが正常にインストールされた後、
改めて問題コマンドの対処を行います。

.bash_profile の最後に以下を追加

```.bash:.bash_profile
alias ping="cocot -p CP932 -t UTF-8 ping"
alias ipconfig="cocot -p CP932 -t UTF-8 ipconfig"
alias nslookup="cocot -p CP932 -t UTF-8 nslookup"
alias tracert="cocot -p CP932 -t UTF-8 tracert"
alias netstat="cocot -p CP932 -t UTF-8 netstat"
alias arp="cocot -p CP932 -t UTF-8 arp"
alias route="cocot -p CP932 -t UTF-8 route"
```

※ -t が CP932, -p が UTF-8 の際はデフォルト指定と同じ為、省略可。


### ssh
　初期状態では ~/.ssh/config を読み込まない。
　.bash_profileに以下を追加。

```.bash:.bash_profile
alias ssh='ssh -F ~/.ssh/config'
alias scp='ssh -F ~/.ssh/config'
```

※フォルダが存在しない際は
　フォルダの作成も実行しておくこと

```
$ mkdir ~/.ssh/
$ touch ~/.ssh/config
```

### sshpass <small>(任意)</small>

~~\~/.ssh/configで鍵管理ができなかった為~~
案件の都合でパスワードの環境がいくつかあった為、
sshpassを通してsshのパスワード入力を飛ばす方法も。

インストール:

```
$ curl -O -L -J https://sourceforge.net/projects/sshpass/files/latest/download
$ tar zxvf sshpass-1.06.tar.gz
$ cd sshpass-1.06
$ ./configure
$ make && make install
```

使い方:

```
$ sshpass -p [パスワード] [接続先情報]
$ sshpass -f [パスワードファイル] [接続先情報]
```

です。

しかしパスワードをコマンドに残すことはしたくないので
ファイル管理を選びたいのですが...
このファイルフォーマットは該当ファイルの一行目のラインを読み込むことしかしません。
(複数の接続先を管理する場合は複数のファイル指定が必要)

その為、以下をコンセプトにして楽に呼び出せるようにしました。

#### 案1: sshp で呼び出し
- 実行コマンド ... sshp [hostname]
- sshの基本設定 ... ~/.ssh/config
- passの保存先 ...  ~/.ssh/sshpass_[hostname]

```.bash:.bash_profile
function _sshp(){
  sshpass -f ~/.ssh/sshpass_${@:$#:1} ssh $@;
}
alias sshp=_sshp
```

上記を定義しておくと `sshp [hostname]` だけで接続ができるようになります。

しかし、これはこれで設定が全て `~/.ssh/` で管理できて楽なのですが、
これでは接続先によって `ssh` と `sshp` を使い分ける必要が出てきて、
若干使い勝手が面倒。その為、この課題を改善する為に案2へ

#### 案2: ssh で一本化

- 実行コマンド ... ssh [hostname]
- sshの基本設定 ... ~/.ssh/config
- passの保存先 ...  ~/.ssh/sshpass_[hostname]

```.bash:.bash_profile
# 上記 sshの基本コマンドはコメント
# alias ssh='ssh -F ~/.ssh/config'

# 基本設定は ~/.ssh/config
function _ssh(){
  if [ -e ~/.ssh/sshpass_${@:$#:1} ]; then
    sshpass -f ~/.ssh/sshpass_${@:$#:1} \ssh -F ~/.ssh/config $@;
  else
    \ssh -F ~/.ssh/config $@;
  fi
}
alias ssh=_ssh
```

こちらにすると、 `~/.ssh/config` に鍵指定をしたかのように
`ssh [hostname]` でも各種接続ができるようになります。

ちなみに、同じように scpを対応してみようとすると、
引数の順番が異なる為、少し大変なことに。
(筆者も組んだばかりで検証不足ですが、簡単な動作は確認)

```.bash:.bash_profile
# 上記 scpの基本コマンドはコメント
# alias scp='scp -F ~/.ssh/config'

function _scp(){
  if [ $# -lt 2 ]; then
    # 引数不足
    \scp -F ~/.ssh/config $@;
    return 0;
  fi

  # 第1引数、第2引数からそれぞれ定義済みパスがあるか確認
  if [[ ${@:$#:1} =~ ^.*:.*$ ]]; then
    local h1=$(echo ${@:$#:1} | sed -e 's/\(^.*\@\)//g' | sed -e 's/\(:.*$\)//g');
    if [ ! -e ~/.ssh/sshpass_$h1 ]; then
      h1='';
    fi
  fi
  if [[ ${@:`expr $# - 1`:1} =~ ^.*:.*$ ]]; then
    local h2=$(echo ${@:`expr $# - 1`:1} | sed -e 's/\(^.*\@\)//g' | sed -e 's/\(:.*$\)//g');
    if [ ! -e ~/.ssh/sshpass_$h2 ]; then
      h2='';
    fi
  fi

  if [ -n "$h1" ] && [ -n "$h2" ]; then
    # 第一引数のみ有効とするかは任意
    echo "sshpassは複数の転送先に対応していません。通常のscpを使用します。";
    \scp -F ~/.ssh/config $@;
  elif [ -n "$h1" ]; then
    sshpass -f ~/.ssh/sshpass_$h1 \scp -F ~/.ssh/config $@;
  elif [ -n "$h2" ]; then
    sshpass -f ~/.ssh/sshpass_$h2 \scp -F ~/.ssh/config $@;
  else
    \scp -F ~/.ssh/config $@;
  fi

}
alias scp=_scp
```

備考：
- sshオプションは ~/.ssh/config で管理されてるなら良いのですが、
　aliasで置き換えられた sshで `$1` を接続先情報として処理すると、
　sshオプション指定時に正しく処理できない可能性があります。
　その為該当箇所はコマンド引数の最後 `${@:$#:1}`を取得するようにすること。
　参考: https://qiita.com/doitnow420@github/items/b449079af841a16170c8
- 案2 の function の中で sshを呼ぶ際は `\` を頭につけないと、
　関数の再起が発生して、無限ループが起きるため注意。


### 最後に

apt-cyg で vimも tmux も普通に入りました。
shellも動き、色々カスタマイズもできるので、
自分好みのターミナルが windows上で作れるかもという印象です。

その為、もしWindowsのsshクライアントで悩まれたら是非一度お試しを。