はじめに (対象読者・この記事でわかること)

この記事は、レガシーな環境でscpやrsyncが使えず、C Shell(csh/tcsh)のみが利用可能なエンジニア向けです。
「古いサーバ同士を繋ぎたいが、セキュアな転送ツールが入っていない」「root権限で新規パッケージを入れられない」といった状況で、cshだけでファイルを転送する方法を探した結果、UUCPとexpectを組み合わせることでなんとかなった経緯を共有します。
この記事を読むと、csh単体ではどこまでできて、どこで諦めて外部ツールを許可すべきか、具体的な判断基準が得られます。

前提知識

  • C Shell(csh/tcsh)の基本構文(if、foreach、here document)
  • UUCPの基本概念(uucp、uux、uunameコマンド)
  • expectスクリプトの基礎(spawn、send、expect、interact)
  • TCP 540番ポート(uucp)がファイアウォールで開いていること

C Shellだけで完結させる夢と現実

2024年、某ISPのレガシー基盤で「scpが使えない」「rsyncはインストール不可」「FTPも動いていない」という三拍子そろった環境が発生しました。
OSはSolaris 10、デフォルトシェルはtcsh、pkgaddで新規パッケージを入れる権限もありません。
「だったらcshで完結させてやる!」と意気込みましたが、最初に突き当たったのは「バイナリデータをどうやって詰め込むか」という壁でした。
ここでは、cshのみで完結させようとした際の「理論上可能な方法」と「現実的に無理だった理由」を整理します。

UUCP+expectで壁を突破する実装

ステップ1 UUCP環境の確認と最低限の設定

まず、両サーバでUUCPが動いているか確認します。
Solaris 10では/etc/uucp以下にConfigファイルが存在する場合が多いです。

Csh
# ローカル側 $ uuname remote1 remote2

相手先がリストに出なければ、相手ホスト名を/etc/uucp/Systemsに追加します。
書式は旧式ですが、以下のように記載します。

remote1 Any ACU 9600 192.168.0.2 ogin: koti word: secret

次に、uucpデーモンが動いているかを確認。

Csh
$ ps -ef | grep uucico root 12345 0 Jun 24 ? 0:00 /usr/lib/uucp/uucico -r1

もしプロセスが見当たらない場合は、以下で起動。

Csh
# Solaris $ /usr/lib/uucp/uucico -r1 & # Linux(古い) $ /usr/sbin/uucico -r1 &

ステップ2 C ShellからUUCPを呼び、転送状況をポーリングする

cshはバックグラウンドジョブの成否を簔状に扱うため、以下のようにwhileループで状態を監視します。

Csh
#!/bin/csh -f set file = $1 set remote = $2 set dest = $3 set lock = /var/spool/lock/uucp.${remote}.lock # 転送開始 uucp -C -j $file ${remote}!${dest} # 転送終了まで待機 while ( -f $lock ) echo -n "." sleep 5 end # ステータスチェック set stat = `uustat | grep $remote | awk '{print $NF}'` if ( $stat == "OK" ) then echo "Transfer completed" else echo "Transfer failed" endif

-C オプションで「コピー元を残す」、-j オプションで「ジョブIDを出力」します。
uustatで「OK」が出るまでwhileループを回すことで、C Shell内で完結します。

ステップ3 expectでパスワード自動入力を組み合わせる

UUCPが古い認証(plain-text)を要求する場合、対話的にパスワードを聞かれます。
cshだけでは応答できないため、expectを経由します。
expectは外部コマンドですが、古いサーバによく残っているため、今回は許容します。

Expect
#!/usr/bin/expect -f set file [lindex $argv 0] set remote [lindex $argv 1] set dest [lindex $argv 2] set timeout 60 spawn uucp -C -j $file ${remote}!${dest} expect "password:" send "secret\r" expect eof

C Shellから呼ぶ場合は、here documentでexpectスクリプトを生成して即実行します。

Csh
#!/bin/csh -f cat > /tmp/xfer$$.exp <<EOF #!/usr/bin/expect -f set file [lindex \$argv 0] set remote [lindex \$argv 1] set dest [lindex \$argv 2] spawn uucp -C -j \$file \${remote}!\${dest} expect "password:" { send "secret\r" } expect eof EOF chmod +x /tmp/xfer$$.exp /tmp/xfer$$.exp $argv[1-3] rm -f /tmp/xfer$$.exp

これで、パスワードをハードコードした自動転送が可能になります。

ハマった点とエラー解決

1. uucpコマンドが「retry: time stamp too far」で失敗する

古いサーバ同士では時刻が数年ずれていることもあります。
UUCPは相手ホストとの時刻差を厳密にチェックするため、以下のエラーが出ます。

uucp: retry: time stamp too far

解決策

/etc/uucp/Configに以下を追記して時刻チェックを緩和します。

MAXTIME 86400

単位は秒なので、1日分の誤差を許容します。
その後、uucpdを再起動。

Csh
pkill -HUP uucico

2. 大容量ファイル(>2 GB)で転送が途中で止まる

32ビット版UUCPはファイルサイズを符号付き32ビットで扱うため、2 GBを超えるとオーバーフローします。
回避策は、splitで小分けして転送後、catで結合することです。

Csh
set size = `stat -c%s $file` if ( $size > 2147483647 ) then split -b 1G -d $file $file.chunk foreach part ( $file.chunk* ) uucp -C -j $part ${remote}!${dest}.$(basename $part) end endif

受信側で以下のように結合。

Csh
cat $dest.chunk* > $dest

3. ファイアウォールでポート540が塞がっている

UUCPデフォルトはTCP 540番です。
セグメントが異なるとファイアウォールで弾かれるため、SSHトンネルで迂回します。

Csh
ssh -f -N -L 1540:localhost:540 relay-server

その上で、/etc/uucp/Systemsのポート番号を1540に書き換えます。

remote1 Any ACU 9600 localhost:1540 ogin: koti word: secret

これで、ファイアウォール越えも可能になります。

まとめ

本記事では、C Shell単体ではサーバ間ファイル転送は現実的に厳しく、UUCPとexpectを組み合わせることでなんとかなったことを紹介しました。

  • cshだけではバイナリ詰め込みや暗号化が困難
  • UUCPなら古い環境でも標準で残っており、uucp/uustatで状態監視が可能
  • expectを経由することでパスワード自動入力を突破
  • 2 GB超えやファイアウォールは分割・SSHトンネルで対応

この記事を通して、「レガシー環境でもツールがゼロではない」「外部ツールを入れられない場合でも、UUCPは最後の砦になる」という選択肢を知っていただければ幸いです。
次回は、UUCP over TLSやSSHベースのuucp-ssh-gatewayを使ったより安全な転送方法を紹介する予定です。

参考資料