実践で学ぶ、一歩進んだサーバ構築・運用術

第 10 回 ssh (中編)


telnet プロキシ

もし,telnet プロトコルを中継する telnet プロキシがファイアウオール上で動いている場合は, Web プロキシの場合と同様にして, telnet プロキシ経由で ssh 接続することもできます。

telnet プロキシとは, 例えば図 1 に示すような手順*3 で, インターネット上のホストへ telnet 接続するためのものです。 この例では telnet プロキシ proxy.klab.org 経由でホスト asao.gcd.org に接続しています。 図 1 の最後の行は, asao.gcd.org の telnet サーバーが送信したログイン・プロンプトです。


kamiya:/home/sengoku % telnet proxy.klab.org
Trying 10.0.0.1...
Connected to proxy.klab.org.
Escape character is '^]'.
proxy.klab.org telnet proxy ready:
Username: sengoku
Password: ########
Login Accepted
telnet-proxy> c asao.gcd.org
Trying 210.145.125.162 port 23...
login:

図 1 telnet プロキシ

telnet プロキシにはいろいろなものがありますが, 大抵はユーザー ID とパスワードを入力して認証を行った後, インターネット上の任意のホスト・ポートに対して TCP/IP 接続する*4, という使い方になります。

図 1 では, ホスト asao.gcd.org の 23 番ポートへ接続していますが, telnet プロキシの多くは, 接続するポート番号を指定できるようです。 この例の場合には, 「telnet-proxy>」プロンプトにおいて図 2 のように入力すれば, asao.gcd.org の 22 番ポートにつなぐことができます。 図 2 の最後の行は, asao.gcd.org の 22 番ポートの ssh サーバーが送信した文字列です。


telnet-proxy> c asao.gcd.org 22
Trying 210.145.125.162 port 22...
SSH-1.99-OpenSSH_2.2.0p1

図 2 telnet プロキシ経由で22 番ポートへつなぐ

proxy-telnet コマンド

以上のような telnet プロキシが利用できる場合, 図 3 の動作を行う proxy-telnet コマンドを作成し, ~/.ssh/config で ProxyCommand を図 4 のように指定すれば, telnet プロキシ経由で ssh 接続できます(図 5)。


(1) telnet プロキシ (図 1 の場合であれば proxy.klab.org の 23 番ポート) に接続する。
(2) 「Username:」を受信するまで待つ。
(3) 「sengoku 」(ユーザーID)を送信する。
(4) 「Password:」を受信するまで待つ。
(5) 「########」(パスワード,伏せ字にしています)を送信する。
(6) 「telnet-proxy>」を受信するまで待つ。
(7) 「c $1 $2 」(インターネット上のホストへの接続コマンド)を送信する。 ただし, $1, $2 はproxy-telnet コマンドの第1 ,第2 パラメータ。
(8) 「...(改行)」を受信するまで待つ。
(9) (8) の「...(改行)」以降に受信したキャラクタを 全てそのまま標準出力へ出力する一方で, 標準入力から入力したキャラクタをすべてそのまま送信する。

図 3 proxy-telnet コマンドの動作


Host   *
    ProxyCommand   /home/sengoku/bin/proxy-telnet %h %p

図 4 ~/.ssh/config で ProxyCommand を指定
telnet プロキシ経由の ssh 接続
図 5 telnet プロキシ経由の ssh 接続

proxy-telnet など ssh の ProxyCommand に指定するコマンドは, どのようなプログラミング言語で記述しても良いのですが, 手軽に書けるという理由から私はいつも Perl を使っています。 Perl だと,TCP/IP 接続,送受信などの手続きが簡潔に記述でき, また図 3 にあるような「〜を受信するまで待ち」という動作が 正規表現を使って書けるので便利です。 perl スクリプトで書いた proxy-telnet コマンドの例を図 6 に示します。


     1  #! /usr/bin/perl
     2  $PROXY_TELNET = "proxy.klab.org";
     3  $PROXY_PORT = 23;
     4  $Verbose = 0;
     5  
     6  while ($_ = shift) {
     7      last if ! /^-(.*)/;
     8      if ($1 =~ /^v+$/) { $Verbose += length($&); next; }
     9      print< 
    11  Options:
    12      -v    Verbose mode
    13  EOF
    14  }
    15  $HOST = $_;
    16  if ($_ = shift) {
    17      $PORT = $_;
    18  } else {
    19      $PORT = 23;
    20  }
    21  print "Verbose Level: $Verbose\n" if $Verbose;
    22  
    23  use Socket;
    24  ($name, $aliases, $proto) = getprotobyname('tcp');
    25  ($name, $aliases, $type, $len, $thataddr) = gethostbyname($PROXY_TELNET);
    26  $that = sockaddr_in($PROXY_PORT, $thataddr);
    27  socket(S, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
    28  connect(S, $that) || die "connect: $!";
    29  
    30  if ($Verbose > 1) {
    31      $Rin = &fhbits('STDIN S');
    32  } else {
    33      $Rin = &fhbits('S');
    34  }
    35  &login;
    36  &connect;
    37  exit 0;
    38  
    39  # login 処理
    40  sub login {
    41      do { &receive(0.1); } until (/name:/);
    42      &send("sengoku\r");
    43      do { &receive(0.1); } until (/word:/);
    44      &send("########\r");
    45      do { &receive(0.1); } until (/proxy>/);
    46      &send("c $HOST $PORT\r");
    47      do { &receive(0.1); } until (/\.\.\.\n/);
    48      $Raw =~ m/\.\.\.[\r\n]+/;
    49      $Raw = $';
    50  }
    51  
    52  # connect
    53  sub connect {
    54      my($rout,$len);
    55      print "CONNECT\n" if $Verbose;
    56      $Rin = &fhbits('STDIN S');
    57      syswrite(STDOUT,$Raw,length($Raw));
    58      while ((select($rout=$Rin,undef,undef,undef))[0]) {
    59          if (vec($rout,fileno(S),1)) {
    60              $len = sysread(S,$_,1024);
    61              return if $len <= 0;        # EOF
    62              syswrite(STDOUT,$_,$len);
    63          }
    64          if (vec($rout,fileno(STDIN),1)) {
    65              $len = sysread(STDIN,$_,1024);
    66              return if $len <= 0;        # EOF
    67              syswrite(S,$_,$len);
    68          }
    69      }
    70  }
    71  
    72  # send(str);  str を送る
    73  sub send {
    74      undef $Buffer;
    75      undef $Raw;
    76      while( $_ = shift ) {
    77          print if $Verbose > 2;
    78          syswrite(S,$_,length);
    79      }
    80  }
    81  
    82  # receive(s);  s 秒入力が途絶えるまで待つ
    83  sub receive {
    84      my($timeout) = shift;
    85      my($rout);
    86      while ((select($rout=$Rin,undef,undef,$timeout))[0]) {
    87          if (vec($rout,fileno(S),1)) {
    88              exit 1 if sysread(S,$_,1024) <= 0;          # EOF
    89              $Raw .= $_;
    90              tr/\r\000\012\021\023\032/\n/d;
    91              $Buffer .= $_;
    92              print if $Verbose > 1;
    93          }
    94          if (vec($rout,fileno(STDIN),1)) {
    95              exit 1 if sysread(STDIN,$_,1024) <= 0;      # EOF
    96              s/\n/\r/g;
    97              syswrite(S,$_,length);
    98          }
    99      }
   100      $_ = $Buffer;
   101  }
   102  
   103  sub fhbits {
   104      my(@fhlist) = split(' ',$_[0]);
   105      my($bits);
   106      for (@fhlist) {
   107          vec($bits,fileno($_),1) = 1;
   108      }
   109      $bits;
   110  }

図 6 Perl で書いた proxy-telnet コマンド

スクリプトには左端の行番号は不要である。

図 6 の 22 行目までは, パラメータの読み込みやデフォルト値の設定です。 表 1 で示した変数が設定されます。

表 1図 6 で用いる変数
変数内容
PROXY_TELNETtelnet プロキシのホスト名
PROXY_PORT telnet プロキシのポート番号
HOST 接続先(ssh サーバー)のホスト名 (第1 パラメータ)
PORT 接続先(ssh サーバー)のポート番号 (第2 パラメータ)
Verbose デバッグ用フラグ (値が1 以上の時,デバッグ・モード)

23 行目から 28 行目までは, telnet プロキシへ TCP/IP 接続を行います。 29 行目以降では, ソケット S に対して読み書きすれば, telnet プロキシへ送受信することができます。 これは図 3 の(1)の動作です。

35 行目の login サブルーチンの呼び出しにおいて, telnet プロキシ経由で接続先の ssh サーバーへの接続を行います。 これは図 3 の(2)から(8)までに対応します。

36 行目の connect サブルーチンの呼び出しは, 標準入力から入力したキャラクタすべてを telnet プロキシへ送信 (ソケット S へ出力)し, telnet プロキシから受信(ソケットS から入力) したキャラクタすべてを標準出力へ出力します。 これは図 3 の(9)に対応します。 connect サブルーチンは, 標準入力あるいはソケット S からの入力のいずれかが EOF になるまで続きます。

39 行目から 50 行目までが login サブルーチンです。 この部分を書き換えれば, どのような telnet プロキシにも対応できます。 図 3 に示した proxy-telnet コマンドの動作と, 図 6 の perl スクリプトの対応関係を, 表 2 に示します。

表 2 proxy-telnet コマンドの動作と perl スクリプトの対応関係
proxy-telnet の動作(図 3 perl スクリプト(図 6)の対応行
(2)「name:」を待つ 41 行目
(3)「sengoku」を送信 42 行目
(4)「word:」を待つ 43 行目
(5)「########」(伏せ字)を送信44 行目
(6)「proxy>」を待つ 45 行目
(7)「c $HOST $PORT」を送信 46 行目
(8)「...(改行)」を待つ 47 行目

49 行目において, 変数「Raw」に「...(改行)」以降のキャラクタ列を代入しています。 これは図 3(9)にあるように, 「...(改行)」以降に受信したキャラクタを標準出力へ出力する必要があるためです。 実際の出力は, connect サブルーチン内の 57 行目で行います。

52 行目から70 行目までが, connect サブルーチンです。 58 行目の select は, 標準入力とソケット S のどちらかからキャラクタが入力されるまで待ちます。

60 行目から 62 行目までが, ソケット S からキャラクタが入力された場合の処理です。 つまり, telnet プロキシからキャラクタを受信した場合です。 EOF であればサブルーチンを終了し(61 行目), そうでなければ受信したキャラクタを, そのまま標準出力に出力します(62 行目)。

65 行目から 67 行目までが, 標準入力からキャラクタが入力された場合の処理です。 EOF であればサブルーチンを終了し(66 行目), そうでなければ入力したキャラクタを, そのままソケット S へ出力します(67 行目)。 つまり telnet プロキシへ送信します。

72 行目から 80 行目までが, キャラクタ列を telnet プロキシへ送信する send サブルーチンです。 単にキャラクタ列をソケット S へ出力するだけですが, 後述する受信用バッファである変数 Buffer と Raw をクリアします。 また,デバッグのために送信キャラクタ列を標準出力へ出力することもできます。

82 行目から 101 行目までが, telnet プロキシからキャラクタ列を受信する receive サブルーチンです。 引数で指定した秒数の間, 受信が途絶えるまで待ちます。 受信したキャラクタ列は変数 Buffer と Raw に設定されます。 変数 Raw は受信したキャラクタ列そのままであり, Buffer はキャラクタ列中の 0x0D(改行)を「\n」へ置き換え, キャラクタ 0x00, 0x0A, 0x11, 0x13, 0x1A を削除したキャラクタ列です。 login サブルーチンで受信キャラクタ列を比較する際, 以上の文字列が入っていると煩雑になるので, このような置き換えと削除を行っています。

86 行目の select はソケット S からの入力を待ちます。 変数 Verbose の値が 2 以上であれば(30 行目から 34 行目), select はソケット S と標準入力からの両方の入力を待ちます。

88 行目から 92 行目までが, ソケット S から入力された場合で, 受信したキャラクタを変数 Buffer と Raw に追加します。 変数 Verbose の値が 2 以上であれば, 変数 Buffer に追加した内容を標準出力に出力します。

95 行目から 97 行目までが, 標準入力からキャラクタが入力された場合で, 入力されたキャラクタはそのまま telnet プロキシに対して送信されます。 この部分はデバッグのためのものです。 例えば,telnet プロキシからの応答が想定したものと異なっていた場合, telnet プロキシへ送るキャラクタ列をキーボードから入力できます。

103 行目から 110 行目までは, select の引数を求めるためのサブルーチンです。 標準入力あるいはソケットS に対応するビットを立てます。

*3
この実行例は架空のもので実在しません。
*4
大企業などの場合, 事業所のファイアウオールと全社のファイアウオールの 2 段構成になっていて, 事業所の telnet プロキシの認証を行った後, 全社の telnet プロキシへ接続しなければならない場合もあるでしょう。

(8 ビット透過でない場合)


本稿は日経Linux 2001 年 1 月号に掲載された、 実践で学ぶ、一歩進んだサーバ構築・運用術, 第 10 回「ssh (中編)」を日経BP 社の許可を得て転載したものです。

Copyright(C)2001 by 仙石浩明 <sengoku@gcd.org>
無断転載を禁じます

| home | up |

sengoku@gcd.org