パチンコ ユニコーン

Tera Termソースコード解説


  1. はじめに
  2. 必要スキル
  3. モジュール構成
  4. ライブラリ構成
  5. プラグインサポート
  6. 設定ファイルの読み書き
  7. セキュアプログラミング
  8. 古いバージョンのWindowsとの互換性維持
  9. デバッグ手法
  10. マルチスレッド
  11. DDEによるプロセス間通信
  12. TTSSHによるSSHの設計と実装
  13. マクロ言語の設計と実装
  14. キャレット制御
  15. シリアルポート
  16. バイナリ転送プロトコル

はじめに

 本文書では、Tera Termのソースコードについて解説をします。解説対象とするソースコードはバージョン"4.58"(2008年2月現在)のものをベースとしています。

必要スキル

 Tera Termのパッケージに含まれるほとんどのプログラムは、C言語で記述されています。一部のコードはC++言語で、MFC(Microsoft Foundation Class)が利用されています。Windows特有の処理を行うために、Win32 APIが多用されているため、APIの知識が必要となってきます。
 ソースコードをビルドするためには、Microsoft Visual Studio 2005 Standard Edition以上が必要です。Express EditionではMFCが利用できないため、ビルドができません。また、C++BuilderやTurbo C++ Explorer、gccなどのコンパイラにおいても、ビルドすることはできません。
 Windowsプログラミングに関する情報の源は、Microsoftが提供する「MSDNライブラリ」にあります。開発を行う際は、MSDNライブラリを頻繁に参照することになります。
 

 ただし、CygTermのみはCygwinのgccでコンパイルをします。ゆえに、CygTermはgccの機能を使った実装になっています。言語はC++です。

 Tera TermのメインエンジンはC++で実装されていますが、C言語的なコーディングがなされているため、Tera Termのソースコードを読み解くには、C言語に関する基礎知識があれば問題ないと言えます。ただし、Microsoft Visual C++(VC++)はANSI C準拠(C89)とはいえ、C99には未対応であるために、本来のC99相当の機能が独自に拡張されている部分もあります。そうした独自拡張された関数には、頭文字にアンダースコア(_)が付いているために、区別が付けやすくなっています。たとえば、VC++の_snprintf()は、ANSI C(C99)のsnprintf()とは似て非なるものです。

モジュール構成

 Tera Termパッケージに含まれる実行モジュール(.exeと.dll)の関連図を以下に示します。実行ファイルの拡張子は".exe"になっており、必要に応じてDLLが動的リンクされます。いずれも32ビットプログラム(x86)であるために、x86-64やIA-64といった64ビット環境ではそのまま動作するかどうかは評価されていません。  
 通常、ユーザがデスクトップやスタートメニューからアプリケーションを起動するときに、呼び出される実行ファイルは"ttermpro.exe"になります。実行ファイルはさらに5つのDLLとダイナミックリンクしています。静的リンクを行い、単一のEXEファイルにしていないのは、1つのプロセスのメモリ占有率を抑えるためです。Tera Termでは多数の起動が行われることが想定されるため、初期設計段階からDLLに分割されています。一度読み込まれたDLLは、複数のプロセス間で共有することができます。
 
   マクロスクリプトを実行する際は、"ttpmacro.exe"というまったく別のプロセスが呼び出されます。"ttermpro.exe"とプロセス単位で分けられているのは、マクロを単体で実行できるようにするためです。両プロセス間で、データのやりとりを行うためには、プロセス間通信が必要です。Tera Termでは、DDE(Dynamic Data Exchange)と呼ばれる現在ではレガシーとなってしまったしくみが採用されています。将来のWindowsではDDEがサポートされなくなる可能性があり、その場合Tera Term上でマクロを実行することは一切できなくなります。
 
   TTSSHやTTProxy、TTXKanjiMenuといったプラグイン形式のDLLは、Tera Termの起動時に明示的に LoadLibrary() を使ってダイナミックロードされます。ロード対象となるDLLのファイル名は、TTXInit()#ttplug.c において、"TTX*.DLL"というパターンにマッチしたものとなります。
 
   "keycode.exe"と"ttpmenu.exe"、"LogMeTT.exe"は単体アプリケーションです。
 
   Cygwin接続のしくみについては、別の節で説明します。

ライブラリ構成

高度な機能を実現するために、フルスクラッチで実装することは効率がいいとは言えません。Tera Termでは開発効率化を図るために、オープンソースのライブラリを積極的に利用しています。ただし、オープンソース製品のライセンスによる競合には注意を払う必要があります(特にGPL)。
下図に、オープンソースのライブラリをリンクしているモジュールと、そのリンク状況を示します。

Tera Termマクロプログラムは、正規表現ライブラリ"Oniguruma"をリンクしています。"waitregex","strmatch","strreplace"コマンドにおいて正規表現を利用するためです。
 Tera Term本体では、バージョンダイアログにOnigurumaのバージョンを表示するためだけにリンクをしています。

Tera Termマクロプログラムは、疑似乱数生成器"SFMT"をリンクしています。"random"コマンドにおいて乱数の生成に利用されています。

SSHモジュールであるTTSSHは、暗号処理を行うために"OpenSSL"をリンクしています。OpenSSLというネーミングからWebアクセスに使われるSSL(Secure Socket Layer)プロトコル専用のライブラリかと思われがちですが、そうではありません。OpenSSLは基本的な暗号アルゴリズムをサポートしており、TTSSHではOpenSSLに含まれる暗号化/復号ルーチンのみを利用しています。このことは、すなわちOpenSSLライブラリにSSL関連のセキュリティホールが発見されたとしても、TTSSHへの影響は極めて低いということです。

SSHモジュールであるTTSSHは、SSHパケットの圧縮を行うために圧縮ライブラリ"zlib"をリンクしています。ただし、ダイヤルアップ回線などの低速度なネットワークにおいては、パケット圧縮は有効ですが、昨今の高速回線ではむしろ速度低下を招く足かせとなります。ゆえに、デフォルトではパケット圧縮機能は無効化されています。

SSHモジュールであるTTSSHは、端末エミュレータ"PuTTY"をリンクしています。PuTTYにはPageantと呼ばれるSSH認証エージェントがあるのですが、TTSSHでPageantによる公開鍵認証をサポートするために、PuTTYのソースコードを利用しています。PuTTYの通信部分および端末エミュレーション部分のソースコードは利用していません。

なお、いずれのライブラリも静的リンク(static link)としています。ライブラリのコンパイルオプションには"/MT"を付加しています。動的リンク(dynamic link)を行うと、一部のユーザ環境でTera Termが起動できないという現象が発生したために、現在では動的リンクは行っていません。


プラグインサポート

 Tera Termでは、DLLという形式でプラグインのしくみをサポートしています。プラグイン形式のDLLファイルを、Tera Termがインストールされているディレクトリへ設置するだけで、Tera Termのソースコードを修正することなく、機能追加を行うことができます。代表的なプラグインとして、TTSSHがあります。
 プラグインを作成するためのサンプルコードとして、TTXSamples\ttxtest\ttxtest.c というソースファイルが用意されています。プラグインを開発するときは、このソースファイルをひな形とするとよいでしょう。実践的なプラグインとして、"TTX KanjiMenu"のソースコード(TTXKanjiMenu\配下)がシンプルで分かりやすいです。

 プラグインは、Tera Term("ttermpro.exe")の起動時に読み込まれます。TTXInit()#ttplug.c が読み込みを行う関数で、カレントディレクトリから"TTX*.DLL"というワイルドカードに合致するDLLファイルが読み込み対象となります。
 複数のDLLが存在する場合は、Tera Term本体からチェインするような形で、各DLLのエクスポート関数が連結されます。連結される順番は、それぞれのDLLが定義するオーダー値(TTXExports構造体のloadOrderメンバ)で決定され、現状下記の通りとなっています。 

モジュール オーダー
TTProxy 0
TTSSH 2500
TTX Kanji Menu 5000

オーダー値が小さいほど、Tera Term本体側に近くなります。たとえば、Tera Term本体からTTXModifyMenu()が呼び出された場合、    
  という順番で、各DLLの関数が呼び出されていくことになります。  
 各DLLが、Tera Term本体側から呼び出してもらうためにエクスポートする関数群は、TTXExports構造体で定義し、TTXBind()で渡します。たとえば、TTX Kanji Menuのエクスポート関数は以下のとおりです。不要な関数は NULL で定義してあります。
static TTXExports Exports = {
/* This must contain the size of the structure. See below for its usage. */
	sizeof(TTXExports),

/* This is the load order number of this DLL. */
	ORDER,

/* Now we just list the functions that we've implemented. */
	TTXInit,
	NULL, /* TTXGetUIHooks */
	NULL, /* TTXGetSetupHooks */
	NULL, /* TTXOpenTCP */
	NULL, /* TTXCloseTCP */
	NULL, /* TTXSetWinSize */
	TTXModifyMenu,
	TTXModifyPopupMenu,
	TTXProcessCommand,
	NULL, /* TTXEnd */
	NULL  /* TTXSetCommandLine */
};
 原則、プラグインのエクスポート関数は、他のプラグインと干渉しないように設計をするべきです。また、Tera Term本体側からの呼び出しが、自分宛てであるかどうかを判断する必要がある場合もあります。
 プラグインがエクスポートする関数について、以下に示します。  

関数 意味
TTXBind 一番始めに呼び出される関数であり、エクスポート関数のテーブルを渡す。
TTXInit TTXBind()の呼び出し後にすぐに実行される関数で、Tera Term本体のグローバル変数(ts, cv)を受け取り、プラグインの初期化を行う。
TTXGetUIHooks ダイアログのハンドルをフックするための関数。Tera Term本体のダイアログインターフェイスを変更したい場合に使う。フック対象の関数は以下のとおり。
&SetupTerminal, &SetupWin, &SetupKeyboard, &SetupSerialPort, &SetupTCPIP, &GetHostName, &ChangeDirectory, &AboutDialog, &ChooseFontDlg, &SetupGeneral, &WindowWindow
TTXGetSetupHooks セットアップルーチンをフックするための関数。フックした側は、元の関数も責任を持って呼び出す必要がある。複数のプラグインが存在する場合、関数がチェインされていく。フック対象の関数は以下のとおり。
&ReadIniFile, &WriteIniFile, &ReadKeyboardCnf, &CopyHostList, &AddHostToList, &ParseParam
TTXOpenTCP TCP接続を行うときに呼び出される関数。シリアル接続のときは呼び出されない。また、以下のソケットインターフェイスをフックすることもできる。
&Pclosesocket, &Pconnect, &Phtonl, &Phtons, &Pinet_addr, &Pioctlsocket, &Precv, &Pselect, &Psend, &Psetsockopt, &Psocket, &PWSAAsyncSelect, &PWSAAsyncGetHostByName, &PWSACancelAsyncRequest, &PWSAGetLastError
TTXCloseTCP TCPコネクションが切断されるときに呼び出される関数。シリアル接続のときは呼び出されない。下記のうちフックしたインターフェイスがあるならば、元に戻す必要がある。
&Pclosesocket, &Pconnect, &Phtonl, &Phtons, &Pinet_addr, &Pioctlsocket, &Precv, &Pselect, &Psend, &Psetsockopt, &Psocket, &PWSAAsyncSelect, &PWSAAsyncGetHostByName, &PWSACancelAsyncRequest, &PWSAGetLastError
TTXSetWinSize Tera Termウィンドウの画面サイズが変更されたときに呼び出される関数。
TTXModifyMenu Tera Termのメニューが初期化されるときに呼び出される関数。プラグイン用のメニューを挿入したい場合に使われる。
TTXModifyPopupMenu Tera Termのポップアップメニューが初期化されるときに呼び出される関数。プラグイン用のポップアップメニューを挿入したい場合に使われる。
TTXProcessCommand メニューが呼び出されたときに実行される関数。プラグイン用のメニューを処理したいときに使われる。
TTXEnd Tera Term本体が終了するときに呼び出される関数。
TTXSetCommandLine 新規接続やセッションの複製を行うときに、コマンドラインパラメータの処理を行うときに呼び出される関数。プラグイン独自のオプションを追加したときは、ここで処理される。

 

設定ファイルの読み書き

 Windowsではアプリケーションのデータ保存のために、レジストリが伝統的に利用されていますが、Tera Termではその誕生がWindows 3.1までに遡るために、.iniファイルによるローカルディレクトリへの保存方法が標準となっています。
 パッケージに同梱されるCollectorやCygTermに関してもローカルディレクトリへデータが保存されます。
 LogMeTTとTTLEditはレジストリへ保存されます。
 例外として、TeraTerm Menuはデフォルトでレジストリへ保存をします。カレントディレクトリに"ttpmenu.ini"(0バイトで可)を設置することで、レジストリの代わりに.iniファイルを使うようにすることもできます。ただし、レジストリから.iniファイルへの移行は自動で行いませんので、手動で再登録してください。
 
   teraterm.iniファイルにエントリを追加した場合は、ReadIniFile()#ttset.cに設定を読み込みするようにします。
	ts->ConfirmChangePaste =
		GetOnOff(Section, "ConfirmChangePaste", FName, TRUE);
 WriteIniFile()#ttset.c に設定を書き込みするようにします。
	WriteOnOff(Section, "ConfirmChangePaste", FName,
		ts->ConfirmChangePaste);
 エントリに文字列を設定する場合は、Win32APIのGetPrivateProfileString()とWritePrivateProfileString()を使います。数値を扱いたい場合は、GetPrivateProfileInt()とWriteInt()を使います。

セキュアプログラミング

文字列操作

 WindowsのデフォルトアカウントはAdministrator権限を保持するために(ただし、Windows Vistaには当てはまらない)、アプリケーションにバッファオーバーフローの不具合があると、管理者権限を第三者に奪取されてしまう危険性があります。
 従来、C言語の文字列処理は開発者のミスにより、バッファオーバーフローが発生しやすいという状況にありました。そこで、MicrosoftはVisual Studio 2005から文字列処理関数のセキュリティ強化バージョンを提供するようになりました。
 

 Tera Termではセキュリティ強化を図るため、文字列操作のほとんどをセキュリティ強化バージョンに置き換えています。以下に代替関数を示します。
 
sprintf(), _snprintf() _snprintf_s()
strcat(), strncat() strncat_s()
strcpy(), strncpy() strncpy_s()
 
   デフォルトのロケールが適用されると、期待する動作とならないケースにおいては、_snprintf_s_l()を使用しています。
 いずれの関数においても、_s("secure")という接尾辞が付くため、見た目に区別が付きやすくなっています。当然のことながら、これらの関数はANSI C非互換です。
 
 なお、これらの関数を利用する際、Count引数(格納する最大文字数)には"_TRUNCATE"マクロを指定しており、バッファオーバーフローが発生する場合は、強制的にバッファの切り詰めを行っています。

 以下に、strncpy_s()の使用例を示します。strncpy_s()の第2引数(numberOfElements)には、ナル文字(\0)も含めたバッファサイズを指定します。書き込み先のバッファは3バイトしかないので、第3引数(strSource)で指定した5バイトのデータは、2バイトに切り詰められ、buf[]には"he\0"が格納されます。

char buf[3];
strncpy_s(buf, sizeof(buf), "hello", _TRUNCATE);
 次に、strncat_s()の使用例を示します。当該関数は、すでに存在する文字列に、さらに文字列を連結するものであるため、第1引数(strDest)はかならずnull-terminateしている必要性があります。strncpy_s()の第2引数(numberOfElements)には、ナル文字(\0)も含めたバッファサイズを指定します。以下の例では、最初の関数を実行すると、5バイト(4文字+ナル文字)が格納されます。2つめの関数を実行する際、残り2バイトしかないので、2文字だけがコピーされ、最終的に"TeraTe"(4文字+2文字+ナル文字)となります。  
char str[7];
str[0] = '\0';
strncat_s(str, sizeof(str), "Tera", _TRUNCATE);
strncat_s(str, sizeof(str), "Term", _TRUNCATE);
 最後に、_snprintf_s()です。紛らわしいのが _snprintf() という関数であり、この関数はnull-terminateされないケースがあるため、使用禁止です。以下に、_snprintf_s()の使用例を示します。以下の例では、buf[]には"ab\0"が格納されます。
char buf[3];
_snprintf_s(buf, sizeof(buf), _TRUNCATE, "abcdef");

古いバージョンのWindowsとの互換性維持

ダイナミックローディング

 Windowsのアプリケーションプログラムは、単一のバイナリファイルを変更することなく、新旧のバージョンのWindows上で起動できるようにするためには、アプリケーションプログラム側での工夫が必要です。
 たとえば、Windows2000で導入された SetLayeredWindowAttributes() APIを直接呼び出すと、WindowsNT4.0や98などではアプリケーションの起動に失敗するようになります。そのため、新しいAPIを呼び出すときは、LoadLibrary()を使って動的ロードするようにします。
static BOOL MySetLayeredWindowAttributes(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags)
{
	typedef BOOL (WINAPI *func)(HWND,COLORREF,BYTE,DWORD);
	static HMODULE g_hmodUser32 = NULL;
	static func g_pSetLayeredWindowAttributes = NULL;

	if (g_hmodUser32 == NULL) {
		g_hmodUser32 = LoadLibrary("user32.dll");
		if (g_hmodUser32 == NULL)
			return FALSE;

		g_pSetLayeredWindowAttributes =
			(func)GetProcAddress(g_hmodUser32, "SetLayeredWindowAttributes");
	}

	if (g_pSetLayeredWindowAttributes == NULL)
		return FALSE;

	return g_pSetLayeredWindowAttributes(hwnd, crKey, 
	                                     bAlpha, dwFlags);
}
 いちいち、手で関数プロトタイプを書いていくのは面倒である場合は、「DLLの遅延読み込み」というしくみを利用すると、上記のような手順は不要です。いきなり、関数を呼び出すことができます。ダイレクトに呼び出したい関数がある場合、それが古いWindowsではサポートされていないものであるならば、Visual Studioのプロジェクト設定で、「DLLの遅延読み込み」に該当するDLLを指定しておきます。

Windows 95

Visual Studio 2005になってから、Windows 95のサポートが打ち切られました。よって、必然的にVisual Studio 2005でビルドしたバイナリは、Windows 95では動かないことになります。参考までに、Visual Studio 2008では Windows 98 と NT4.0 、2000 のサポートが打ち切られ、Visual Studio 2010でも同様です。今後は、Windows XPもサポートされなくなることが予想されます。

現在、Tera TermではVisual Studio 2005によりビルドされていますが、とある工夫により Windows 95 でも動作するようになっています。もちろん、Microsoft非公認の方法であるため、Microsoftからの正式なサポートは受けられません。
そもそも、Visual Studio 2005 でビルドされたバイナリは、デフォルトで IsDebuggerPresent 関数にリンクしてしまっています。当該関数は Windows 98 からサポートされたAPIであるため、Windows 95ではリンクエラーとなるわけです。
そこで、Windows 95において、ダミーで IsDebuggerPresent 関数のシンボルを定義してあげれば、プログラムの起動時にエラーになることはなくなるのです。詳細は"comapt_w95.h"ヘッダを参照してください。


デバッグ手法

debug printf

 Windowsアプリケーションでは printf() が使えません。標準出力がどこにも割り当てられていないからです。AllocConsole()とfreopen()を使えば、Windowsアプリケーションにおいても printf() を利用することができます。
 OutputDebugString()というAPIがあります。これは Visual Studio のデバッグコンソールにメッセージ出力することができる関数です。当該APIは、"Debug build"および"Release build"に関係なく、デバッガが存在すれば、メッセージを送信します。ゆえに、 Visual Studioがなくとも、のようなツールを使えば、アプリケーションの単体起動においても、OutputDebugString()によるメッセージを拾うことができます。
 Tera Termでは、可変長引数を扱えるようにラッパー関数を用意しています。  
void OutputDebugPrintf(char *fmt, ...) {
	char tmp[1024];
	va_list arg;
	va_start(arg, fmt);
	_vsnprintf_s(tmp, sizeof(tmp), _TRUNCATE, fmt, arg);
	OutputDebugString(tmp);
}

memory leak

 malloc()等による確保したヒープメモリの解放し忘れによる「メモリリーク」を、自動で検出するしくみが Visual Studio には用意されています。プログラムの起動時に、以下のコードを挿入するだけです。プログラムの終了時に、解放していないヒープメモリがあれば、 Visual Studio の「出力」ウィンドウにリストアップされます。
#ifdef _DEBUG
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
 なお、Windowsのように仮想記憶で動くアプリケーションプログラムに関しては、プログラムの終了時に解放されていないメモリが存在した場合、OSが面倒を見て、メモリが解放されるようになっています。

マルチスレッド

 Windowsのアプリケーションはマルチスレッドで設計されることがほとんどですが、Windows 3.1から95の時代ではあまり一般的ではありませんでした。そのため、元々Tera Termはマルチスレッド化されていません。ソースコードを見ると分かるように、グローバル変数が多用されているため、ほとんどの処理がスレッドセーフではありません。
 ただし、一部の処理においては _beginthreadex() API を使ってスレッドが生成されています。以下にスレッド生成箇所を示します。

Tera Term
生成箇所 ソースファイル
シリアル接続 CommStart()#commlib.c
TELNETキープアライブ TelStartKeepAliveThread()#telnet.c
IPv4/v6ソケットの生成 WSAAsyncGetAddrInfo()#WSAAsyncGetAddrInfo.c

TTSSH
生成箇所 ソースファイル
SSHキープアライブ start_ssh_heartbeat_thread()#ssh.c
SCP送信処理 SSH2_scp_tolocal()#ssh.c
SCP受信処理 SSH2_scp_fromremote()#ssh.c

 すでに説明したとおり、Tera Term(TTSSH含む)の内部処理はスレッドセーフではないため、シンプルにスレッドを生成し、スレッド内から送受信処理等を行おうとすると、不具合が発生してしまいます。
 TELNETやSSHのキープアライブ(ハートビート)処理を実現するためには、定期的にパケットの送信処理を行う必要があります。また、SCPによるファイル送受信を行う際においても、ファイルの送信処理中に、ユーザの端末操作のレスポンスを落とさないために、スレッドの使用が不可欠です。
 そこで、マルチスレッドを使う場合は、モードレスダイアログを非表示で作成したあとに、_beginthreadex() APIでスレッドを生成し、実際の処理はモードレスダイアログに行わせるという手段を使用しています。このしくみにより、マルチスレッドを使いながら、スレッドセーフを保つことができます。以下に、コード例を示します。
#define WM_SEND_HEARTBEAT (WM_USER + 1)

static INT_PTR CALLBACK telnet_heartbeat_dlg_proc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
{

	switch (msg) {
		case WM_INITDIALOG:
			return FALSE;

		case WM_SEND_HEARTBEAT:
			TelSendNOP();
			return TRUE;
			break;

		case WM_COMMAND:
			break;

		case WM_CLOSE:
			return TRUE;

		case WM_DESTROY:
			return TRUE;

		default:
			return FALSE;
	}
	return TRUE;
}

static unsigned _stdcall TelKeepAliveThread(void *dummy) {
  static int instance = 0;

  if (instance > 0)
    return 0;
  instance++;

  while (cv.Open && nop_interval > 0) {
    if (time(NULL) >= cv.LastSendTime + nop_interval) {
		SendMessage(keepalive_dialog, WM_SEND_HEARTBEAT, 0, 0);
    }

    Sleep(100);
  }
  instance--;
  return 0;
}

void TelStartKeepAliveThread() {
  unsigned tid;

  if (ts.TelKeepAliveInterval > 0) {
    nop_interval = ts.TelKeepAliveInterval;

    keepalive_dialog = CreateDialog(hInst, MAKEINTRESOURCE(IDD_BROADCAST_DIALOG),
               HVTWin, telnet_heartbeat_dlg_proc);

    keepalive_thread = (HANDLE)_beginthreadex(NULL, 0, TelKeepAliveThread, NULL, 0, &tid);
    if (keepalive_thread == 0) {
      keepalive_thread = INVALID_HANDLE_VALUE;
      nop_interval = 0;
    }
  }
}

DDEによるプロセス間通信

概要

 DDE(Dynamic Data Exchange)の誕生は、1987年のWindows 2.0までに遡ります。DDEはプロセス間通信を行うためのしくみですが、現在ではレガシーな方式であり、ほとんどのアプリケーションでは利用されていません。Windowsにおけるプロセス間通信といえば、メールスロットや名前付きパイプ、OLEなどが定番です。
 かつては、DDEによるプロセス間の通信データをキャプチャすることができる「DDEスパイ」(DDESPY.EXE)というツールがVisual Studioに付属していましたが、現在のVisual Studioにはもはや含まれていません。
 DDEに関するリファレンスはMSDNライブラリから参照することができます。

 DDEは、TCPによるネットワーク通信と似ており、サーバとクライアント間を一対一で接続し、通信を行います。アプリケーションがDDEによる通信を行うために、DDEML(Dynamic Data Exchange Management Library)と呼ばれるライブラリをWin32 APIとして提供されています。
 DDE通信を行うために、一方がサーバとなり、他方がクライアントになる必要があります。また、通信のセッションをシステム全体でユニークとするために、識別情報が必要です。TCP通信ではIPアドレスとポート番号が使われますが、DDE通信では「サービス名」と「トピック名」の組み合わせが使われます。Tera Termではサービス名は"TERATERM"という文字列が使われ、トピック名はTera Term本体のウィンドウハンドル(HVTWin)の16進数値を文字列化したものが使われています。
 このようなしくみになっているために、マクロスクリプトからまったく別のTera Termへコマンドを送ることはできません。
 上図に示すように、Tera Term本体("ttermpro.exe")がDDEサーバとなり、マクロプログラム("ttpmacro.exe")がDDEクライアントとなります。DDEでは、やりとりするデータの塊のことを「トランザクション」と呼びます。トランザクションには以下に示すような何種類かがあります。タイプは"ddeml.h"でマクロ定義されています。

タイプ 意味
XTYP_ADVREQ DDEサーバがクライアントへデータを送るために、DDEサーバが自分自身に送るメッセージ。
XTYP_POKE DDEクライアントからサーバへデータを送る。
XTYP_ADVSTART DDEサーバに対してアドバイズループの開始を指示する。
XTYP_ADVDATA DDEクライアントにデータを定期的に送る。
XTYP_EXECUTE DDEサーバに文字列を送り、何らかの処理をサーバに指示する。

 DDE通信の特徴として、アドバイズループ(advise loop)という概念があります。DDEサーバがアドバイズループに入ると、クライアントはサーバから定期的にデータを受け取り続けることができます。Tera Termでは、リモートホストからの受信データを、マクロプログラムへ渡すために、アドバイズループが使われています。

ライブラリ

 Tera Termで使われているDDEMLについて、以下に示します。    

関数名 機能
DdeInitialize DDEを初期化し、コールバック関数を登録する。初期化できるとインスタンスを返す。
DdeCreateStringHandle 文字列リテラルからハンドルを作成する。ハンドルはサーバとクライアントの通信用に使われる。
DdeNameService インスタンスとサービス名("TERATERM")をサーバに登録する。登録後、XTYP_REGISTERトランザクションがクライアントへ送られる。登録解除する際にも使われる。
DdeCmpStringHandles 2つの文字列ハンドルを比較する。
DdeClientTransaction クライアントからサーバへトランザクションを送ることができる。トランザクションタイプとして、XTYP_REQUEST・XTYP_EXECUTE・XTYP_ADVSTART・XTYP_POKEなどが指定できる。サーバからのACKを待つまでのタイムアウト時間を指定することができ、Tera Termではほとんど"1000ミリ秒(1秒)"が指定されている。ただし、ACKを確認するケースにおいては"5000ミリ秒(5秒)"が指定されている。
DdeAccessData DDEハンドルから実際のデータへのポインタを取得する。データの取り出しが終わったら、DdeUnaccessData()を呼び出すこと。
DdeCreateDataHandle DDEオブジェクトを作成し、ハンドルを返す。DDEサーバのアドバイズループや、XTYP_REQUESTトランザクション受信時に、DDEクライアントへデータを送るために使われている。
DdeGetData DDEオブジェクトからバッファへコピーする。
DdeDisconnect DDE通信を終了する
DdePostAdvise DDEサーバ側で使われる関数で、自分自身に XTYP_ADVREQ トランザクションを送る。

実装

 DDEサーバ側の実装について見ていきます。Tera Term本体("ttermpro.exe")がDDEサーバとなり、かならずDDEサーバから起動されます。マクロプログラム("ttpmacro.exe")から直接マクロスクリプトが実行されるケースにおいても、"connect"マクロによりDDE接続をしないと、通信が開始できません。
 Tera TermのControlメニューからMacroを呼び出した場合、RunMacro()#ttdde.c がコールされます。
 HVTWinウィンドウハンドルからトピック名(8バイト)を作成し、DDEの初期化とサーバの登録を行います。また、このタイミングでDDEバッファ(1KB)を作成しています。その後、"ttpmacro.exe"を /D= オプションでトピック名を渡しつつ、起動をします。
 
	SetTopic();
	if (! InitDDE()) return;
	strncpy_s(Cmnd, sizeof(Cmnd),"TTPMACRO /D=", _TRUNCATE);
	strncat_s(Cmnd,sizeof(Cmnd),TopicName,_TRUNCATE);
 DDEサーバに、DDEクライアントからトランザクションが送られてきたときは、DdeCallbackProcコールバック関数が呼び出されます。コールバック関数は、DdeInitialize()でDDEの初期化を行うときに登録されます。

   次に、DDEクライアントについて見てみましょう。マクロプログラムの起動時、InitDDE()#ttmdde.c が呼び出され、DDEクライアントとして初期化が行われます。DDEの初期化は、DdeInitialize()で行われ、同時にDdeCallbackProcコールバック関数が登録されます。DDEサーバから届いたトランザクションは、コールバック関数で処理されます。
 DDE通信を始めるためには、DdeConnect()を呼び出し、サーバと接続する必要があります。次に、"ttpmacro.exe"のウィンドウハンドル(HWin)をサーバへ通知するために、XTYP_EXECUTEトランザクションで送ります。最後に、XTYP_ADVSTARTトランザクションをサーバへ送り、アドバイズループを開始します。
  ConvH = DdeConnect(Inst, Service, Topic, NULL);
  if (ConvH == 0) return FALSE;
  Linked = TRUE;

  Cmd[0] = CmdSetHWnd;
  w = HIWORD(HWin);
  Word2HexStr(w,&(Cmd[1]));
  w = LOWORD(HWin);
  Word2HexStr(w,&(Cmd[5]));

  DdeClientTransaction(Cmd,strlen(Cmd)+1,ConvH,0,
    CF_OEMTEXT,XTYP_EXECUTE,1000,NULL);

  DdeClientTransaction(NULL,0,ConvH,Item,
    CF_OEMTEXT,XTYP_ADVSTART,1000,NULL);

バッファの管理

 マクロプログラムでは"wait"コマンド等で、リモートホストから送られてきたデータを監視するための機能が用意されています。この機能を実現するためには、Tera Term本体とマクロプログラムのそれぞれにおいて、バッファを用意する必要があり、プロセス間通信(DDEトランザクション)により、Tera Term本体からマクロプログラムへリモートホストからの受信データを送らなければなりません。
 まず、Tera Term本体におけるリモートホストからのTCPパケット受信は、アイドルループ OnIdle()#teraterm.cpp にて行われます。OnIdle()から呼び出される CommReceive()#commlib.c において、TCPパケットデータをバッファ(cv->InBuff[])に格納します。このバッファは 1KB の大きさを持ちます。また、リングバッファではないため、バッファフルになった場合は、TCPパケットの受信をしません。ただし、バッファフル状態が長く続くと、Windowsカーネル内にTCPパケットが溜まっていき、いずれはリモートホストからのパケットを受信できなくなる可能性があります。
 エスケープシーケンスの解析処理を行う過程で、「ログ採取」か「マクロ実行」を行っている場合は、LogPut1()が呼び出され、DDEバッファ(cv.LogBuf[])へ受信データが格納されます。すなわち、ログ採取とマクロ実行におけるバッファは共通です。このバッファは1KBの大きさを持つリングバッファであり、バッファフルになった場合は、最古のデータから上書きされてゆきます。
 なお、バイナリモードでログ採取においては、cv.BinBuf[] という別のバッファへデータが格納されますので、DDEバッファとは別物です。言い換えると、バイナリモードにおけるデータをDDE通信させることはできないということです。単純な"wait"コマンドでは、バイナリデータ(制御コードなど)を待つことはできません。
 Tera Term本体のDDEバッファのデータは、エスケープシーケンスの解析処理が完了後、DDEAdv()#ttdde.c がすぐに呼び出され、自分自身(DDEサーバ)へ XTYP_ADVREQ トランザクションを送ります。XTYP_ADVREQを受け取ったら、DDEコールバック関数 DdeCallbackProc() が呼び出され、マクロプログラムへのデータ送信を行います。ここでアドバイズループが使われています。
 アドバイズループによりDDEサーバよりデータが送られてくると、DDEクライアントであるマクロプログラムにおいては、XTYP_ADVDATAトランザクションがDDEコールバック関数 DdeCallbackProc()#ttmdde.c により処理されます。
   なお、Tera Term本体において、DDE通信用のバッファと、ログ採取用のバッファは cv.LogBuf[] で共有されています。バッファの先頭とデータサイズを表すインデックスは、DDE通信の場合は"DStart"と"Dcount"、ログ採取の場合は"LStart"と"Lcount"と区別されています。実際には、1つのバッファを共有しているわけなので、それぞれのインデックスが食い違うと、誤動作する原因となるため、常に同期を取っておくことになります。

TTSSHによるSSHの設計と実装

概要

 オリジナルのTTSSHは氏(現在はとして活躍)により開発されたプラグインです。SSH1へ対応しており、ポートフォワーディングやzlibによるパケット圧縮もサポートしていました。TTSSHは、Tera Termをセキュア通信に対応させるためのプラグインであったために、SCPやSFTP等には未対応でした。オリジナルTera Termが1998年に開発凍結後も、2001年ごろまでメンテナンスが続けられていました。
 TTSSHのSSH2対応を実現するために、TeraTerm Projectにより2004年から設計と実装が始められました。3年の歳月をかけて、ほぼSSH2プロトコルのフルサポートを実現しました。現在ではSCPへも対応しています。将来的にはSFTPへも対応されるかもしれません。
 原則、TTSSHの実装はを参考にしています。一部、コードをそのまま流用しているところもあります。ただし、OpenSSHはUNIXのコマンドライン向けに設計されているため、Tera TermのようなWindowsアプリケーションにはそのまま適合しない箇所も多く、フレームワークとしてはOpenSSHと大きく異なったものとなっています。

SSHプロトコル

 SSH(Secure Shell)は、バージョン1(厳密には1.5)とバージョン2が存在し、略して"SSH1"および"SSH2"と呼ばれます。それらのバージョン間にはプロトコル仕様としての互換性はありません。SSH1にはセキュリティ上の問題があるために、現在はほとんど利用されません。
 SSH2プロトコルの仕様に関しては、RFC化されています。  

接続処理

 TTSSHは、Tera Termの一部のコードでもあるため、ネットワーク接続処理はTera TermとTTSSHの間を行き来することになり、処理の流れが複雑になっています。また、SSHプロトコルそのもののフローを熟知していないと、TTSSHのシーケンスを追っていくのが難しくなっています。以下に、リモートホストへの接続を行うまでのフローを示します。

送信パケット処理

SSH2プロトコルに載せて、パケットをサーバへ送るときのコードは以下のような書き方となります。begin_send_packet()の呼び出しで、「pvar->ssh_state.outbuf + 12」が返り値となり、それがペイロードを表します。ペイロードは純粋にサーバへ送りたいデータのことで、サイズやパディング等を含みません。
	buffer_t *msg;
	int len;
	char *s;
	unsigned char *outmsg;
	
	msg = buffer_init();
	if (msg != NULL) {
		buffer_put_int(msg, SSH2_DISCONNECT_PROTOCOL_ERROR);
		s = "disconnected by server request";
		buffer_put_string(msg, s, strlen(s));
		s = "";
		buffer_put_string(msg, s, strlen(s));

		len = buffer_len(msg);
		outmsg = begin_send_packet(pvar, SSH2_MSG_DISCONNECT, len);
		memcpy(outmsg, buffer_ptr(msg), len);
		finish_send_packet(pvar);
		buffer_free(msg);
	}
 SSH通信に載せられて、実際にパケットが送出されるのは、finish_send_packet()から呼び出される finish_send_packet_special() です。パケットを送信するときのフォーマットについて、以下に示します。共通鍵暗号でパケットデータを暗号化する前に、ヘッダとフッタを付ける必要があります。
 パケットサイズはHMACを除く長さです。パケットサイズそのものはビッグエンディアン形式で、4バイト分格納しますが、その"4"バイトは含まれません。ペイロードの直後にパディングを埋めるのは、共通鍵暗号で暗号化するときに「ブロックサイズ単位」になっていなければ、アルゴリズム的に暗号化できないからです。ブロックサイズは暗号アルゴリズムにより異なり、たとえば3DES-CBCならば24バイト、AES128ならば16バイトです。
 HMAC(Keyed-Hashing for Message Authentication)は、暗号化本文に対するハッシュです。ハッシュのアルゴリズムは選択可能であり、"MD5"や"SHA-1"がよく使われています。HMACを付加することにより、「第三者によるデータの改ざん」を検出することができます。HMACは、暗号化対象となる本文を秘密鍵とシーケンス番号を加え、ハッシュ値を計算します。秘密鍵とシーケンス番号を加えることにより、第三者がデータをまるごと差し替えたとしても、送信者が生成したハッシュ値を復元することは理論上できません。
 
 zlibによるパケット圧縮を行う場合における、パケットを送信するときのフォーマットについて、以下に示します。パケット圧縮を行うのは、「ペイロード」の部分のみで、残りは通常の送信パケットとフォーマットは同じです。なお、パケットを圧縮したとしても、かならずしも元のサイズよりも小さくなるとは限らないので、そのことを考慮したバッファ管理が必要です。
 パケット圧縮送信で難しいのは、圧縮を開始するタイミングです。ローカルホストからリモートホストへのSSH接続を開始すると、実にたくさんのネゴシエーションが行われますが、パケットを圧縮してよいのは決められたタイミングであり、このタイミングを間違えると、サーバとまったく通信ができなくなります。
 通常のパケット圧縮の場合は、"SSH2_MSG_KEXINIT"を受信したタイミングです。遅延パケット圧縮の場合は、ユーザ認証が成功したタイミング("SSH2_MSG_USERAUTH_SUCCESS"を受信した時)です。遅延パケット圧縮というのは、それまで"SSH2_MSG_KEXINIT"を受信したタイミングで圧縮を開始していたのを、ユーザ認証が完了するまで延長する方式です。遅延パケット圧縮は、zlibライブラリのセキュリティホールにより、不正なSSHサーバへ接続しただけで、クライアント側に影響が出るのを回避するためのしくみです。

受信パケット処理

 パケットの受信は、TeraTerm本体側からは recv ソケット関数を呼び出した場合に、それがTELNETなのかSSHなのかを意識させないような設計になっていることと、 recv ソケット関数の呼び出しでは、かならずしも十分なバッファサイズが指定されてくるとは限らないため、少々実装が複雑になっています。
 TeraTerm本体側は OnIdle()#teraterm.cpp というアイドルループにおいて、常時パケットの受信がないかをポーリングしています。それが CommReceive() で、recv()を呼び出します。recv()はTTSSHによりフックされているので、ソケット関数ではなく、TTXrecv()#ttxssh.c が呼び出されます。
 CommReceive()は recv() を呼び出す際に、バッファ(cv->InBuff[])の空きポインタとサイズを引数に渡します。バッファサイズは 1KB です。つまり、TTXrecv()のサイズには、1〜1024 までの数値が渡される可能性があるということです。
 TTXrecv()から呼び出される PKT_recv() は、少々複雑なループ処理となっています。SSH接続を初めて行うときのシーケンスを以下に示します。  
  1. recv_data() で本当の recv() を呼び出し、サーバからの受信パケットをカーネルから受け取る。pvar->pkt_state.datalenが更新される。
  2. SSH_handle_server_ID() でSSHサーバのバージョンチェックが行われる。pvar->pkt_state.datastart と pvar->pkt_state.datalen を更新する。
  3. 再度、recv_data() が呼ばれるが、サーバからの受信データがもうないので、connection_closed=TRUE として while ループを抜ける。
  4. TeraTermの recv() は"0"で返ってくる。すなわち、受信データなし。
 次に、SSH通信のための共通鍵生成までのシーケンスを以下に示します。
  1. recv_data() で本当の recv() を呼び出し、サーバからの受信パケットをカーネルから受け取る。pvar->pkt_state.datalenが更新される。
  2. SSH_predecrpyt_packet() で、受信パケットの先頭ブロックのみを復号化する。SSHパケットのサイズを取得する。
  3. 妥当なSSHパケットサイズならば、SSH_handle_packet() を呼び出し、メッセージタイプに応じたハンドラを呼び出す。pvar->ssh_state.payload と pvar->ssh_state.payloadlen を設定する。
  4. pvar->pkt_state.datastart と pvar->pkt_state.datalen を更新する。
  5. pvar->pkt_state.datalen がゼロになるまで、SSH_predecrpyt_packet() の処理を繰り返す。
  6. recv_data() が呼ばれるが、サーバからの受信データがもうないので、connection_closed=TRUE として while ループを抜ける。
  7. TeraTermの recv() は"0"で返ってくる。すなわち、受信データなし。
 次に、端末データ通信のシーケンスを以下に示します。
  1. recv_data() で本当の recv() を呼び出し、サーバからの受信パケットをカーネルから受け取る。pvar->pkt_state.datalenが更新される。
  2. SSH_predecrpyt_packet() で、受信パケットの先頭ブロックのみを復号化する。SSHパケットのサイズを取得する。
  3. 妥当なSSHパケットサイズならば、SSH_handle_packet() を呼び出し、メッセージタイプに応じたハンドラを呼び出す。pvar->ssh_state.payload と pvar->ssh_state.payloadlen を設定する。
  4. メッセージタイプがSSH2_MSG_CHANNEL_DATAなので、handle_SSH2_channel_data() を呼び出す。pvar->ssh_state.payload_datalen と pvar->ssh_state.payload_datastart を設定する。
  5. pvar->pkt_state.datastart と pvar->pkt_state.datalen を更新する。
  6. SSH_is_any_payload() が真を返すようになり、PKT_recv()に渡されてきたバッファへデータをコピーする。
  7. TeraTerm側のバッファサイズがいっぱいになった場合は、SSH端末データが残っていたとしても、PKT_recv()は返る。
  8. TeraTerm側のバッファサイズに余裕がある場合は、recv_data()を呼び出し、サーバからの受信データを取得する。
  9. TeraTermの recv() は「受信データサイズ」で返ってくる。

シーケンス制御

 SSHは通信経路を暗号化することができるのが特徴ですが、パケットの暗号化を行うためには「鍵」が必要です。通信経路の暗号化には、共通鍵による共通鍵暗号が利用されます。公開鍵暗号のほうがセキュリティ強度は高いのですが、暗号処理に多大な時間がかかるため、SSHのような通信性能が要求されるしくみでは採用されません。SSH2では、共通鍵暗号アルゴリズムとして、AES(Advanced Encryption Standard:Rijndaelアルゴリズム)や3DES(Triple DES)などが利用されます。
 共通鍵は通信を行う二者間でのみに共有される情報であり、第三者に知られてはなりません。SSH2では、クライアントがリモートホスト(SSHサーバ)へTCP接続した時に、"Diffie-Hellman"アルゴリズムをベースとした独自の方式により、クライアントとサーバでしか知り得ない共通鍵を共有します。DH鍵共有が完了するまでの過程は、ネットワーク上をパケットが平文で流れるため、第三者によるパケットキャプチャが可能となっていますが、パケットを覗かれても、共通鍵は理論上第三者には分からないようになっています。
 共通鍵が共有できたあとは、その鍵を使ってパケットを暗号化します。SSH2では、送受信されるパケットは種類があるため、それぞれに「メッセージ番号」を割り振っています。RFC4250にメッセージ番号の一覧があります。メッセージ名は"SSH2_MSG_xxxx"というネーミングになっており、TTSSH内部でも同じ名前でマクロ定義しています。
 以下に、クライアントからサーバへTCP接続(ポート22番)してから、パスワード認証でユーザ認証されるまでの流れを示します。
 以下は、リモートホストのシェル上で"exit"や"logout"として、クライアントから明示的にシェルをクローズするときの、パケットの流れを示しています。

疑似端末のしくみ

 SSH2では、新しく「フロー制御」という概念が取り込まれています。TCPのウィンドウと同じ考え方で、「ウィンドウサイズ」というしくみを導入しています。この機能により、クライアント(Tera Term)とサーバ(SSHデーモン)間において、フロー制御が働くため、原則データが溢れることはありません。
 ところで、SSH2におけるフロー制御があるにも関わらず、大量のクリップボードをTeraTermの端末へペーストすると、サーバ側での「データの取りこぼし」が発生することがあります。この現象を理解するためには、UNIXにおける疑似端末(PTY: pseudo-terminal)の動作原理を知る必要があります。
 SSHデーモン(sshd)はクライアントに対して、あたかもサーバ側のシェルが直接接続されているかのように見せる必要があります。逆に、シェル上で動くプログラムは、文字を送りたいときは printf(3) を、文字を受け取りたい場合は scanf(3) といったCライブラリ関数を呼び出すだけでよく、その先がシリアルコンソールなのか、VGAコンソールなのか、SSH接続されているのかは、一切気にしなくてよいようになっています。
 sshdは、クライアントからの接続要求があったタイミングで、openpty(3)を使って、疑似端末の初期化を行います。疑似端末では、カーネル空間でクライアントとサーバをつなぐために、「マスターデバイスドライバ」と「スレーブデバイスドライバ」が用意されます。マスターデバイスドライバが担当するデバイスファイルは"/dev/pty[p-za-e][0-9a-f]"、スレーブデバイスドライバでは"/dev/tty[p-za-e][0-9a-f]"です。つまり、sshdはマスターデバイスドライバへアクセスすることで、シェルとお話をすることができます。シェルは、sshdからforkされて子プロセスとなり、親プロセス(sshd)が初期化済みのスレーブデバイスドライバとお話をすることになります。この疑似端末のしくみにより、sshdとシェルが接続されます。
 なお、端末ラインディシプリン(line discipline: 回線規約)というのは、たとえばプログラムが getchar() を呼び出したときに、Enterキーを押下するまで、プログラムに制御が渡りません。端末ラインディシプリンは、プログラム実行中での「行内編集」を可能とするためのモジュールです。Linuxでは、端末ラインディシプリンは /proc/tty/ldiscs で確認できます(N_TTYが標準的に利用される)。

SCP(Secure Copy)

 SCPは OpenSSH パッケージに含まれるプログラムの1つであり、SSHセッションを使ってファイルの送受信を行うことができます。SCPを利用するためには、リモートサーバに"sshd"だけではなく、"scp"コマンドも導入されている必要があります。OpenSSHのSCPは、sshd デーモンから"scp"コマンドが子プロセスとして起動されることで実現されています。なお、SCPとSFTP(Secure File Transfer Program)はまったく別のプロトコルで、互換性はなく、SCPは純粋にファイルの「送信」と「受信」しかできません。
 SSHセッション上でファイル転送を行うには、クライアントからサーバへ接続が成功したあとに、シェルオープン(pty-req)の代わりに、「外部コマンドの実行」(exec)という形式で、SCPが利用できるようになります。    

・SSH2の場合

 SSH2_MSG_CHANNEL_REQUEST をサーバへ送るときに、"pty-req"の代わりに"exec"をサービス名として指定すると、外部コマンドを実行することができます。
  ユーザ認証成功後
       ----> SSH2_MSG_CHANNEL_OPEN(90)
       <---- SSH2_MSG_CHANNEL_OPEN_CONFIRMATION(91)
       ----> SSH2_MSG_CHANNEL_REQUEST(98)  サービス名"exec"で外部コマンド送信("scp -f")
       <---- SSH2_MSG_CHANNEL_WINDOW_ADJUST  (remote_window+=131072バイト)
       <---- SSH2_MSG_CHANNEL_EXTENDED_DATA  (local_window-=36バイト)
       <---- SSH2_MSG_CHANNEL_DATA(94)
 

・SSH1の場合

  セッションを開くときに、SSH_CMSG_EXEC_CMD をサーバへ送ると、外部コマンドを実行することができます。    

・外部コマンドの書式

 * "scp [-v] [-r] [-p] [-d] -t ファイル名" ローカルからリモートへのコピー
 * "scp [-v] [-r] [-p] [-d] -f ファイル名" リモートからローカルへのコピー
    -v verbose
    -r リカーシブ
    -p タイムスタンプ保持
    -d ディレクトリ
    -t Local-to-Remoteへコピー
    -f Remote-to-Localへコピー
 

・データ転送

  外部コマンドの送信が完了したあとに、ファイルの内容を送信および受信することができます。
  1.送信の流れ
    ・"Tタイムスタンプ 0 タイムスタンプ 0"を送信(オプション)
    ・"C0666 サイズ ファイル名"を送信
    ・ファイルの内容を送信
    ・セッションクローズ

  2.受信の流れ
    ・"Tタイムスタンプ 0 タイムスタンプ 0"を受信(オプション)
    ・0を送信
    ・"C0666 サイズ ファイル名"を受信
    ・0を送信
    ・ファイルの内容を受信
    ・ファイルのタイムスタンプを設定(オプション)
    ・0を送信
    ・セッションクローズ
 

・注意事項

  ファイル名にディレクトリが含まれるときは、パスの区切りは「/」となります。「\」は受け付けないので、変換が必要です。

X11転送

 X11転送(X11 port forwarding)は、SSHサーバ上でXウィンドウアプリケーションを起動し、アプリケーションのGUI画面をTera Termが動作しているコンソールPCに飛ばすしくみです。このしくみを使うと、SSHセッション上で"xeyes"や"firefox"、"xemacs"などのソフトウェアを動かすことができるようになります。なお、コンソールPC上には、Xming(//sourceforge.net/projects/xming/)などのXサーバをあらかじめ用意しておく必要があります。
 下図にX11転送のフローを示します。図を見ると分かるように、Tera Term(TTSSH)はXアプリケーションとXサーバをつなぐ橋渡しの役目を持ちます。このようなTera Termのことを"Redirector"や"Port forwarder"、"TCP proxy"と呼びます。
 X11転送を利用するためには、Tera TermおよびSSHサーバの双方に事前設定が必要です。まず、Tera Termのほうはteraterm.iniに下記の設定が必要です。
[TTSSH]
DefaultForwarding=X
 SSHサーバのほうは、OpenSSHを例に挙げると、"sshd_config"に下記の設定が必要です。デフォルトは"no"になっているため、通常はデフォルトではX11転送が使えません。
X11Forwarding=yes
 Tera TermはX11転送が有効であると、spec.typeに"FWD_REMOTE_X11_TO_LOCAL"を設定します。これはSSHサーバ側からTera Term側に向かって、X11転送を行うことを意味します。Tera Termは、リモートホストにSSH接続する際、セッションオープン後の"SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"において、X11転送の初期化を行います。
	if (c->type == TYPE_SHELL) {
		// ポートフォワーディングの準備 (2005.2.26, 2005.6.21 yutaka)
		// シェルオープンしたあとに X11 の要求を出さなくてはならない。(2005.7.3 yutaka)
		FWD_prep_forwarding(pvar);
		FWD_enter_interactive_mode(pvar);
	}
 FWD_prep_forwarding()では、"x11-req"サービス名と"MIT-MAGIC-COOKIE-1"をSSHサーバに送信し、SSHサーバ側のX11転送の初期化を促します。SSH接続時にX11の初期化が完了すると、SSHサーバ側に環境変数"DISPLAY"が自動的に設定されます。
# echo $DISPLAY
DISPLAY=localhost:10.0
 ここまで準備が整うと、SSHサーバ上でXアプリケーションを起動させることができます。XアプリケーションからXサーバ、すなわちSSHサーバからTera Termへ送られてくるデータは、SSH2_MSG_CHANNEL_DATA メッセージ形式となります。当該メッセージは FWD_received_data() で処理され、Xサーバ(TCP/6000)へ送られます。Xサーバのソケットは channel->local_socket で、ノンブロッキングモードで扱われます。そのため、一度でパケットを全部送れない場合があるため、送れなかったデータは内部バッファに溜めておく必要があります。また、channel->local_socket にパケットをsendすることにより、FD_WRITE メッセージが発生し、write_local_connection_buffer() が呼び出されます。ここでは、前回送れなかったデータがあれば、内部バッファから取り出し、再度Xサーバへの送信を試みます。
 反対に、Xサーバ、すなわちX11の画面上で何らかの操作が行われた場合、Tera TermからSSHサーバにデータを送信する必要があります。このとき、Tera Termへは FD_READ メッセージが発生し、read_local_connection() が呼び出されます。ここでは、Xサーバから送られてきたデータを SSH2_MSG_CHANNEL_DATA メッセージ形式に載せて、SSHサーバへ送ります。

マクロ言語の設計と実装

概要

 Tera Termのマクロスクリプトは、BASIC風の言語仕様となっています。BisonやFlexといったしくみは利用しておらず、力業的な独自の構文解析(再帰的下降法)により実装されています。そのため、本格的なスクリプト言語としての記述はできない側面があります。
 

ファイルの読み込み

 ttpmacro.exeの起動時に、マクロファイル(.ttl)が一括してバッファへ読み込まれます。  

 初めて読み込まれるマクロファイルの全内容は Buff[0] # ttmbuff.c に格納されます。この時点で、ファイルの内容は一括して読み込まれるため、マクロ実行中はファイルを削除してしまっても問題はありません。ただし、"include"で別のファイルを読み込む場合は、includeを実行する時点で、include対象となるファイルの読み込みが発生します。  
#define MAXNESTLEVEL 10     /* 扱えるファイル数(includeは9つまで)*/

static int INest;     /* 現在のネスト位置 */
static HANDLE BuffHandle[MAXNESTLEVEL];   /* GlobalAlloc()によるバッファ */
static PCHAR Buff[MAXNESTLEVEL];          /* バッファ領域 */
static BINT BuffLen[MAXNESTLEVEL];        /* ファイルサイズ(バッファサイズ) */
static BINT BuffPtr[MAXNESTLEVEL];        /* バッファのオフセット(読み込み位置)*/

マクロエンジン

 マクロ処理はアイドルループ OnIdle()#ttmmain.cpp で行われます。アイドルループでは TTLStatus 変数により、マクロエンジンの動作を変えています。通常の実行状態は IdTTLRun がセットされています。以下に、動作一覧を示します。

条件 処理
TTLStatus==IdTTLEnd マクロプログラムを終了する
送信データがある場合(OutLen > 0) Tera Term本体へデータを送る
TTLStatus==IdTTLRun 一行ずつマクロを実行する
TTLStatus==IdTTLWait ウェイトする('wait'コマンド)
TTLStatus==IdTTLWaitLn ウェイトする('waitln'コマンド)
TTLStatus==IdTTLWaitNL 一行受信する('recvln'コマンド)
TTLStatus==IdTTLWait2 文字列を待つ('waitrecv'コマンド)

インタープリタ処理

 アイドルループから Exec()#ttl.c が定期的に呼び出される度に、マクロファイルが一行ずつ処理されてゆきます。GetNewLine() では、バッファから一行分を取り出し、LineBuff[]#ttmparse.c へ格納します。行の終わりかどうかは、「ASCIIコードが0x20未満で、かつタブ(0x09)以外」のコードが出現したタイミングで判定しています。先頭の空白やタブは無視されます。セミコロン(;)が出現すると、以降の処理をスキップするため、コメントは行の途中でも付けられることになります。
char LineBuff[MaxLineLen];      /* 1つの行は500バイトまで格納可能 */
WORD LinePtr;       /* バッファオフセット */
WORD LineLen;       /* バッファサイズ */
 Exec()から呼ばれる ExecCmnd() で、字句解析を行います。字句解析は単純な文字列検索であり、LineBuff[]を1バイトずつ参照していきます。大まかな処理の流れは以下のとおりです。

  1. endwhileの判定
  2. break処理
  3. endifの判定
  4. elseの判定
  5. マクロコマンドの実行
  6. 識別子の判定
  7. 文法エラー(上記のいずれでもない場合)

 マクロコマンドかどうかは、GetReservedWord()で判別しています。_stricmp()で比較しているので、アルファベットの大文字・小文字は区別されません(case-insensitive)。マクロコマンドの場合は、TTLxxx() の関数を呼び出します。
 識別子の判定は、GetIdentifier() で行います。アルファベット(a-z, A-Z)および数値(0-9)、アンダースコア(_)から構成されるトークンを切り出します。トークンは32文字までです。トークンは「変数」として扱われます。左辺値に変数が来る場合は、「変数への代入」しかありえないので、その直後に「イコール(=)」があるかどうかを調べます。
 イコール以降の判定処理は、以下の順番となります。  

  1. 文字列の判定
  2. 計算式の判定

 文字列かどうかは GetString() で判定します。文字列は’か”でクォートされているため、取り出すのは容易です。
 計算式の判定は、GetExpression() で行います。ここでは再帰的下降法により、構文解析されます。
 左辺値が定義済みの変数かどうかは CheckVar() でチェックし、数値もしくは文字列をセットします。そうではない場合は NewStrVar() で、新しい変数として登録します。  

キャレット制御

概要

 ユーザが端末上でキーボード入力を行うと、カーソルが移動しますが、サーバからのエスケープシーケンスにより、キーボード入力なしにカーソルを移動させる必要があります。また、ウィンドウが非アクティブ状態の場合においても、カーソルを表示させることにより、ブロードキャストモードにおいて、複数端末の同時操作性を向上させています。

システムキャレット

 Tera Termにおけるカーソル描画には、システムキャレットを利用しています。Tera Termで使用されているシステムキャレットを制御するAPIを以下に示します。

 によると、
システムは 1 つのキューにつき 1 つのキャレットを提供します。ウィンドウが
キーボードフォーカスを備えているとき、またはアクティブな状態のときにだけ、
キャレットを作成するべきです。また、キーボードフォーカスを失ったり非アク
ティブになる前に、キャレットを破棄するべきです。
とあるため、ウィンドウがアクティブになったタイミングで CreateCaret() を呼び出し、フォーカスが外れ、非アクティブになるタイミングで DestroyCaret() を呼び出す必要があることを意味しています。
 キャレットの表示は CaretOn()#vtdisp.c で、消去は CaretOff()#vtdisp.c で実装されています。CaretOn()やCaretOff()が呼び出されるタイミングは、エスケープシーケンス処理 VTParse() の箇所以外にも、マウスボタンを押したときやウィンドウのリサイズを行っているときなどがあります。

非アクティブ時のカーソル表示

 ウィンドウが非アクティブの場合は、カーソルが消滅します。Windowsの上ではユーザが操作できうるウィンドウは1つであるため、システムキャレットも1つのみ用意されています。通常のオペレーションにおいては、この動作で問題がありません。
 しかし、ブロードキャストモードを利用する場合、非アクティブのTera Termウィンドウに対して、コマンドを投入することになります。特に、viなどで複数の端末を同時操作するときは、カーソルが消えていると不都合があります。
 そこで、ウィンドウが非アクティブの場合においても、カーソルを描画するようにしています。ただし、システムキャレットは使えないので、自前でカーソルを描画する必要があります。Tera Termのウィンドウが非アクティブの場合においても、リモートホストから送られてくるエスケープシーケンスを処理するためにメインエンジンは動いており、常にカーソル位置は更新されています。現在のカーソル位置は、CursorXとCursorYに設定されています。
 非アクティブ時のカーソル表示は CaretKillFocus() で行っています。このときに表示されるカーソルを「ポリゴンカーソル」と呼んでいます。ts.VTColor[0] は Text color です。非アクティブ状態でカーソル位置が更新されるときは、以前に描いたカーソルを消す必要があるので、そのときは ts.VTColor[1] で表される Background color で再描画することで、以前のカーソルを消去しています。
 Background colorでポリゴンカーソルを描画すると、ちょうどそのとき背景にあった文字の一部が欠けることがあります。そのため、その文字の再描画を行う必要があり、UpdateCaretKillFocus() で実現しています。当該関数では InvalidateRect() で WM_PAINT を送ることにより、文字の再描画を促しています。
void CaretKillFocus(BOOL show)
{
  int CaretX, CaretY;
  POINT p[5];
  HPEN oldpen;
  HDC hdc;

  DispInitDC();
  hdc = VTDC;

  CaretX = (CursorX-WinOrgX)*FontWidth;
  CaretY = (CursorY-WinOrgY)*FontHeight;

  p[0].x = CaretX;
  p[0].y = CaretY;
  p[1].x = CaretX;
  p[1].y = CaretY + FontHeight - 1;
  if (CursorOnDBCS)
	p[2].x = CaretX + FontWidth*2 - 1;
  else
	p[2].x = CaretX + FontWidth - 1;
  p[2].y = CaretY + FontHeight - 1;
  if (CursorOnDBCS)
	p[3].x = CaretX + FontWidth*2 - 1;
  else
	p[3].x = CaretX + FontWidth - 1;
  p[3].y = CaretY;
  p[4].x = CaretX;
  p[4].y = CaretY;

  if (show) {  // ポリゴンカーソルを表示(非フォーカス時)
	  oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, ts.VTColor[0]));
  } else {
	  oldpen = SelectObject(hdc, CreatePen(PS_SOLID, 0, ts.VTColor[1]));
  }
  Polyline(VTDC, p, 5);
  oldpen = SelectObject(hdc, oldpen);
  DeleteObject(oldpen);

  DispReleaseDC();
}

非アクティブ時のカーソル表示タイミング

 非アクティブ時のカーソル表示のタイミングは、いくつかのパターンがあるため、漏れなく対処しておく必要があります。表示タイミングとしては以下のとおりです。



シリアルポート

概要

 Tera TermはUART(16550A)互換のシリアルポートに対応しているため、シリアルコンソールが使用できます。シリアルポートのことを、COM(Communication Port)ポートと呼ぶこともあります。OSが検出したCOMポートは、順に"COM1"、"COM2"といった名前が付けられ、アプリケーションから利用することができます。Microsoft Windows XPでは、最大256個のCOMポート(COM1〜COM256)までが利用可能です。
 パソコンに搭載されるCOMポートは、せいぜい1つ、多くても2つであり、最近ではまったくCOMポートがないパソコンも存在します。そのため、USB接続によるシリアルポートを実現する「USBシリアル変換ケーブル」が発売されています。こういった製品の特徴として、OSに認識させるCOMポートの番号を、ユーザが自由に設定できるようになっています。すなわち、Tera Term見えには、2つのCOMポートがあった場合、それぞれ"COM1"、"COM2"として認識できるとは限らず、"COM1"、"COM7"といったふうに認識できるようになる必要があります。
 

COMポートのリストアップ

 かつてのTera Termでは、"COM1"から"COM256"までのすべてのCOMポートを、接続ダイアログにリストアップしていましたが、使い勝手がよくありませんでした。そこで、接続ダイアログを呼び出したタイミングにおいて(Tera Term起動時のみでは不十分)、OSが認識しているCOMポートを検出するようにして、必要なCOMポートのみを表示させるようにしました。その検出ロジックが、DetectComPorts()#ttcmn.c です。QueryDosDevice() APIを使用し、MS-DOSデバイス名から"COM"を探します。
	if (((h = GetModuleHandle("kernel32.dll")) != NULL) &&
	    (GetProcAddress(h, "QueryDosDeviceA") != NULL) &&
	    (QueryDosDevice(NULL, devicesBuff, 65535) != 0)) {
		p = devicesBuff;
		while (*p != '\0') {
			if (strncmp(p, "COM", 3) == 0 && p[3] != '\0') {
				ComPortTable[comports++] = atoi(p+3);
				if (comports >= ComPortMax)
					break;
			}
			p += (strlen(p)+1);
		}

COMポートのフルネーム取得

 上記の処理だけでもユーザビリティは向上するのですが、さらなる欲求として、各COMポートに付けられる「フルネーム」を同時に表示したくなります。COMポートの番号とともに、フルネームも付加表示できると、さらに使い勝手がよくなることが期待されます。この課題を解決するのが、ListupSerialPort()#ttcmn.c です。
 
static void ListupSerialPort(LPWORD ComPortTable, int comports, char **ComPortDesc, int ComPortMax)
{
	GUID ClassGuid[1];
	DWORD dwRequiredSize;
	BOOL bRet;
	HDEVINFO DeviceInfoSet = NULL;
	SP_DEVINFO_DATA DeviceInfoData;
	DWORD dwMemberIndex = 0;
	int i;

	DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

	bRet =
		SetupDiClassGuidsFromName(_T("PORTS"), (LPGUID) & ClassGuid, 1,
		                          &dwRequiredSize);
	if (!bRet) {
		goto cleanup;
	}

	DeviceInfoSet =
		SetupDiGetClassDevs(&ClassGuid[0], NULL, NULL, DIGCF_PRESENT | DIGCF_PROFILE);

	if (DeviceInfoSet) {
		dwMemberIndex = 0;
		while (SetupDiEnumDeviceInfo
		       (DeviceInfoSet, dwMemberIndex++, &DeviceInfoData)) {
			TCHAR szFriendlyName[MAX_PATH];
			TCHAR szPortName[MAX_PATH];
			DWORD dwReqSize = 0;
			DWORD dwPropType;
			DWORD dwType = REG_SZ;
			HKEY hKey = NULL;

			bRet = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
			                                        &DeviceInfoData,
			                                        SPDRP_FRIENDLYNAME,
			                                        &dwPropType,
			                                        (LPBYTE)
			                                        szFriendlyName,
			                                        sizeof(szFriendlyName),
			                                        &dwReqSize);

			hKey = SetupDiOpenDevRegKey(DeviceInfoSet,
			                            &DeviceInfoData,
			                            DICS_FLAG_GLOBAL,
			                            0, DIREG_DEV, KEY_READ);
			if (hKey) {
				long lRet;
				dwReqSize = sizeof(szPortName);
				lRet = RegQueryValueEx(hKey,
				                       _T("PortName"),
				                       0,
				                       &dwType,
				                       (LPBYTE) & szPortName,
				                       &dwReqSize);
				RegCloseKey(hKey);
			}

			if (_strnicmp(szPortName, "COM", 3) == 0) {  // COMポートドライバを発見
				int port = atoi(&szPortName[3]);
				int i;

				for (i = 0 ; i < comports ; i++) {
					if (ComPortTable[i] == port) {  // 接続を確認
						ComPortDesc[i] = _strdup(szFriendlyName);
						break;
					}
				}
			}
		}
	}

cleanup:
	SetupDiDestroyDeviceInfoList(DeviceInfoSet);
}

バイナリ転送プロトコル

概要

パソコン通信時代に、バイナリファイルを転送するためのプロトコルが多数開発され、Tera Termではいくつかの転送方式をサポートしています。これらのプロトコルは、今となってはレガシー仕様であり、ほとんど利用されることはありません。現在では、ルータなどの組み込み機器において、ファームウェアのアップロードに使われるぐらいです。 本節では、XMODEM/YMODEM/ZMODEMに関して説明します。

仕様

XMODEMの登場は1977年とかなり古く、また仕様がシンプルであるがゆえ、オリジナルの仕様(Ward Christensen氏が開発)を改版した亜種が多数存在しました。その後、XMODEMを改良したYMODEM(Chuck Forsberg氏が開発)が登場し、さらにYMODEMの欠点を改善したZMODEM(Chuck Forsberg氏が開発)が1986年に開発されました。
このように仕様自体が、どこかの団体により規定されたわけではないため、XMODEM/YMODEM/ZMODEMの実装にはクセがあります。Tera TermがサポートするXMODEM/YMODEM/ZMODEMはベーシックなものですが、接続相手によってはうまく動かないことも多く、ユーザによるコンフィグレーションが必要となる場合があります。

階層構造

バイナリ転送プロトコルを容易に追加できるようにするため、各モジュールは階層構造になっています。
      +-------------------------------------------------------+
      |ttermpro.exe (filesys.cpp)                             |
      +-------------------------------------------------------+
      |ttpfile.dll (ttfile.c)                                 |
      +-------+--------+--------+--------+--------+-----------+
      |Kermit | XMODEM | YMODEM | ZMODEM | B-Plus | Quick-VAN |
      +-------+--------+--------+--------+--------+-----------+
たとえば、XMODEMの送信メニューを選択した場合、処理のフローは以下のようになります。
filesys.cpp: OnFileXSend() -> XMODEMStart() -> OpenProtoDlg() ->
ttfile.c: ProtoInit() ->
xmodem.c: XInit()
ZMODEMの受信メニューの処理に関しては、以下のとおりです。
filesys.cpp: OnFileZRcv() -> ZMODEMStart() -> OpenProtoDlg() ->
ttfile.c: ProtoInit() ->
zmodem.c: ZInit()

エントリポイント

いかなるプロトコルを実装しようとも、ttpfile.dllにおける関数インターフェイス(エントリポイント)が用意されていれば、容易に新規プロトコルとして組み込むことができるようになっています。エントリポイントは、ProtoInit()・ProtoParse()・ProtoTimeOutProc()・ProtoCancel()から呼び出されます。
XMODEMのエントリポイントについて、以下に示します。
関数 意味
XInit 初期化
XSendPacket ファイル送信
XReadPacket ファイル受信
XTimeOutProc タイムアウト処理
XCancel キャンセル処理

ZMODEMのエントリポイントについて、以下に示します。
関数 意味
ZInit 初期化
ZParse ファイル送信
ZParse ファイル受信
ZTimeOutProc タイムアウト処理
ZCancel キャンセル処理

テスト手法

 バイナリ転送プロトコルはシリアル回線で利用されることが多いのですが、最近のPCは、シリアルポートが搭載されていないため、シリアル接続でのテストを行うことが難しくなっています。そこで、を利用すると、1つのPC内で仮想的に2つのCOMポートを生成し、Tera Term同士、Tera Termと別ターミナルソフト同士で、シリアル通信を行うことができます。  

記号

 バイナリ転送プロトコルでは、ACKやCANといったキャラクタ表記が使われますが、これらはASCIIコード表から来ています。man 7 ascii でASCIIコード一覧が参照できます。以下に代表的な記号と値を引用します。  
Oct   Dec   Hex   Char                        Oct   Dec   Hex   Char
------------------------------------------------------------------------
001   1     01    SOH (start of heading)      101   65    41    A
002   2     02    STX (start of text)         102   66    42    B
004   4     04    EOT (end of transmission)   104   68    44    D
006   6     06    ACK (acknowledge)           106   70    46    F
025   21    15    NAK (negative ack.)         125   85    55    U
030   24    18    CAN (cancel)                130   88    58    X

XMODEM

XMODEMは、ファイルのデータを一定のサイズ(128バイトおよび1024バイト)に分割し、ブロックごとにACKを確認しながら、送信を行うプロトコルです。ブロック単位で、毎回ACKを確認するため、転送速度は速くはありませんが、実装がシンプルとなります。
最後のブロックが一定のサイズに満たない場合は、満たすようにCPMEOF(0x1A)がパディングされます。すなわち、データを送ると、かならずデータのサイズが一定のサイズの倍数となり、末尾にCPMEOFが付加される場合があるということです。そのため、ファイルの送信に完全性を求める場合は、XMOMDEは使えません。なお、CPMEOFというのは、MS-DOSの前身であるCP/MというOSにおいて、テキストファイルの終端(EOF)を表す値のことです。
 XMODEMのプロトコルについては、下記サイトが参考になります。
 teraterm.ini で XmodemLog エントリを有効にすると、通信ログを採取することができます。通信ログファイルは、ttermpro.exe と同じディレクトリに"XMODEM.LOG"という名前で生成されます。
; XMODEM log
XmodemLog=on
 簡単な例として、Tera Term(COM10)から(COM11)に対して、67バイトのファイルを送信した場合の通信ログを示します。「<<<」行はTera Termがホストから受信したデータで、「>>>」行はTera Termが送信したデータです。  
<<<
15                                                  .

>>>
01 01 FE 23 0D 0A 23 20 6B 6E 6F 77 6E 5F 68 6F     ...#..# known_ho
73 74 73 20 66 69 6C 65 20 66 6F 72 20 54 54 53     sts file for TTS
53 48 28 41 6E 20 53 53 48 20 45 78 74 65 6E 73     SH(An SSH Extens
69 6F 6E 20 74 6F 20 54 65 72 61 20 54 65 72 6D     ion to Tera Term
29 0D 0A 23 0D 0A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     )..#............
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A EC                                         ....

<<<
06                                                  .

>>>
04                                                  .

<<<
06 
 上記ログの意味は以下のとおりです。  
  1. NAK受信(15)
  2. ブロックデータの送信
  3. ACK受信(06)
  4. EOTの送信(04)
  5. ACK受信(06)
 ブロックデータは、「ヘッダ(3byte)+データ(128byte)+CRC(1byte)」から構成されます。この例では、送信データが128byte未満なので、パディングとしてCPMEOF(0x1A)で埋められています。

 次はもう少し大きめのサイズ(1772byte)のデータを送信してみます。(1772+127)/128=14 で、14回ブロック転送されるはずです。下記に通信ログを示します。ヘッダの第2バイトがブロック番号(1オリジン)であり、1(0x01)〜14(0x0E)まで増加していることが分かります。最後のブロックは128バイトに収まるように、CPMEOFがパディングとして付加されています。
 このようにXMODEMによる通信では、送信後ファイルの末尾にゴミが付いたように見えるので、厳密にはファイルが壊れます。
<<<
15                                                  .

>>>
01 01 FE 3B 20 73 61 6D 70 6C 65 20 6D 61 63 72     ...; sample macr
6F 20 6F 66 20 54 65 72 61 20 54 65 72 6D 0D 0A     o of Tera Term..
3B 0D 0A 3B 20 46 69 6C 65 3A 20 73 63 72 65 65     ;..; File: scree
6E 63 61 70 74 75 72 65 2E 74 74 6C 0D 0A 3B 20     ncapture.ttl..; 
44 65 73 63 72 69 70 74 69 6F 6E 3A 20 63 61 70     Description: cap
74 75 72 65 20 73 63 72 65 65 6E 20 63 6F 6E 74     ture screen cont
65 6E 74 73 20 61 6E 64 20 77 72 69 74 65 20 74     ents and write t
6F 20 66 69 6C 65 0D 0A 3B 20 45 6E 76 69 72 6F     o file..; Enviro
6E 6D 65 F4                                         nme.

<<<
06                                                  .

>>>
01 02 FD 6E 74 3A 20 67 65 6E 65 72 69 63 0D 0A     ...nt: generic..
3B 20 55 70 64 61 74 65 3A 20 32 30 30 37 2F 31     ; Update: 2007/1
31 2F 32 35 2C 20 31 32 2F 35 2C 20 32 30 30 38     1/25, 12/5, 2008
2F 30 31 2F 33 30 0D 0A 3B 20 41 75 74 68 6F 72     /01/30..; Author
3A 20 49 57 41 4D 4F 54 4F 20 4B 6F 75 69 63 68     : IWAMOTO Kouich
69 20 28 64 6F 64 61 29 2C 20 59 75 74 61 6B 61     i (doda), Yutaka
20 48 69 72 61 74 61 0D 0A 3B 20 54 69 70 73 3A      Hirata..; Tips:
0D 0A 3B 20 20 20 49 74 20 69 73 20 72 65 63 6F     ..;   It is reco
6D 6D 65 CA                                         mme.

<<<
06                                                  .

>>>
01 03 FC 6E 64 65 64 20 74 68 61 74 20 79 6F 75     ...nded that you
20 77 69 6C 6C 20 61 64 64 20 69 6E 20 74 68 65      will add in the
20 66 6F 6C 6C 6F 77 69 6E 67 20 65 6E 74 72 79      following entry
0D 0A 3B 20 20 20 69 6E 20 60 4B 45 59 42 4F 41     ..;   in `KEYBOA
52 44 2E 43 4E 46 27 20 66 69 6C 65 20 62 65 63     RD.CNF' file bec
61 75 73 65 20 79 6F 75 20 63 61 6E 20 63 61 70     ause you can cap
74 75 72 65 20 79 6F 75 72 20 73 63 72 65 65 6E     ture your screen
0D 0A 3B 20 20 20 61 74 20 6F 6E 65 27 73 20 66     ..;   at one's f
69 6E 67 9C                                         ing.

                    :
                    :
                    :

<<<
06                                                  .

>>>
01 0E F1 73 70 72 69 6E 74 66 20 22 73 63 72 65     ...sprintf "scre
65 6E 63 61 70 74 75 72 65 5F 25 73 25 73 25 73     encapture_%s%s%s
2D 25 73 25 73 25 73 2E 74 78 74 22 20 44 61 74     -%s%s%s.txt" Dat
65 59 20 44 61 74 65 4D 20 44 61 74 65 44 20 54     eY DateM DateD T
69 6D 65 48 20 54 69 6D 65 4D 20 54 69 6D 65 53     imeH TimeM TimeS
0D 0A 66 69 6C 65 6E 61 6D 65 20 3D 20 69 6E 70     ..filename = inp
75 74 73 74 72 0D 0A 72 65 74 75 72 6E 0D 0A 1A     utstr..return...
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 75                                         ...u

<<<
06                                                  .

>>>
04                                                  .

<<<
06 

YMODEM

 YMODEMは、XMODEMを改良したプロトコルです。XMODEMとの違いはいくつかありますが、大きな違いの1つとして、ファイル情報が送れるということです。YMODEMでは、ファイル名やファイルサイズをホストに知らせることができるので、送信したファイルの末尾からCPMEOFを除去することができます。
 YMODEMのプロトコルについては、下記サイトが参考になります。
 teraterm.ini で YmodemLog エントリを有効にすると、通信ログを採取することができます。通信ログファイルは、ttermpro.exe と同じディレクトリに"YMODEM.LOG"という名前で生成されます。
; YMODEM log
YmodemLog=on
 簡単な例として、Tera Term(COM10)から(COM11)に対して、67バイトのファイルを送信した場合の通信ログを示します。「<<<」行はTera Termがホストから受信したデータで、「>>>」行はTera Termが送信したデータです。  
<<<
43                                                  C

>>>
02 00 FF 73 73 68 5F 6B 6E 6F 77 6E 5F 68 6F 73     ...ssh_known_hos
74 73 00 36 37 20 31 31 31 36 32 32 30 30 31 30     ts.67 1116220010
30 20 31 30 30 36 34 34 00 00 00 00 00 00 00 00     0 100644........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 1B 08                                      .....

<<<
06 43                                               .C

>>>
02 01 FE 23 0D 0A 23 20 6B 6E 6F 77 6E 5F 68 6F     ...#..# known_ho
73 74 73 20 66 69 6C 65 20 66 6F 72 20 54 54 53     sts file for TTS
53 48 28 41 6E 20 53 53 48 20 45 78 74 65 6E 73     SH(An SSH Extens
69 6F 6E 20 74 6F 20 54 65 72 61 20 54 65 72 6D     ion to Tera Term
29 0D 0A 23 0D 0A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     )..#............
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A     ................
1A 1A 1A 6D 7A                                      ...mz

<<<
06                                                  .

>>>
04                                                  .

<<<
06 43                                               .C

>>>
02 00 FF 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00     ................
00 00 00 00 00                                      .....

<<<
06 
 上記ログの意味は以下のとおりです。  
  1. 'C'(送信要求)受信(43)
  2. ブロック0(ファイル情報)の送信
  3. ACK受信(06)
  4. 'C'(送信要求)受信 (43)
  5. ブロック1の送信
  6. ACK受信 (06)
  7. EOTの送信 (04)
  8. ACK受信(06)
  9. 'C'(送信要求)受信 (43)
  10. ブロック0(オールゼロ)の送信
  11. ACK受信(06)

ZMODEM

TBD

KERMIT

 KERMIT(カーミット:セサミストリートに登場するカエルのマペット)は、1981年にコロンビア大学で開発されたファイル転送プロトコルであり、現在はカーミットプロジェクトによりメンテナンスされています。下記のサイトから仕様書が入手できます。
 
 上記サイトでは、ソースコードも配布されており、様々なプラットフォームに実装されています。実装の名称としては、C-KermitやE-Kermit、Kermit95などがあります。
 teraterm.ini で KmtLog エントリを有効にすると、通信ログを採取することができます。通信ログファイルは、ttermpro.exe と同じディレクトリに"KERMIT.LOG"という名前で生成されます。  
; Kermit log
KmtLog=on
 KERMITは元々低速なシリアル通信を想定しているため、一度に送れるデータサイズはせいぜい94バイトです。ただし、拡張オプションで数千バイトのデータを扱うことはできますが、クライアントとサーバの両方が当該機能をサポートしている必要があります。
 パケットのフォーマットは基本形式と拡張形式があり、仕様書の「Appendix I Packet Format and Types」に分かりやすい図解があります。下記に引用します。
 以下は基本形式です。94バイトまでしか扱えません。
Basic Kermit Packet Layout
       |<------Included in CHECK------>|
       |                               |
+------+-----+-----+------+------ - - -+-------+
| MARK | LEN | SEQ | TYPE | DATA       | CHECK |<terminator>
+------+-----+-----+------+------ - - -+-------+
             |                                 |
             |<--------LEN-32 characters------>|

  MARK   パケットの先頭。CTRL-A(0x01)が入る。
  LEN    パケットサイズ+32。"LEN+2"バイトが全体サイズとなる。
  SEQ    「シーケンス番号+32」の剰余64。シーケンス番号は0〜63まで。
  TYPE   大文字アルファベットでパケットの種別を表す。
  DATA   データ
  CHECK  加算チェックサム。1,2,3バイトのいずれかの形式を選べる。
  <terminator>   制御コード
 以下は拡張形式です。
Kermit Extended Packet Layout
       |<-------------------------Included in CHECK------------->|
       |                                                         |
       |<-------Included in HCHECK------->|                      |
       |                                  |                      |
+------+-----+-----+------+-------+-------+--------+----- - - - -+-------+
| MARK |     | SEQ | TYPE | LENX1 | LENX2 | HCHECK | DATA        | CHECK |
+------+-----+-----+------+-------+-------+--------+----- - - - -+-------+
        blank                                      |                     |
                                                   |<------------------->|
                    LX1=LENX1-32, LX2=LX2-32 95 x LX1 + LX2 chars
HCHECK is a single-character type 1 checksum
 拡張形式では94バイト以上のデータを一度に送れるようにするため、データサイズを表現する領域が2バイトに増えています。基本形式の"LEN"は常にゼロです(32を加算するので、ASCIIコードの空白になる)。また、ヘッダサイズが3バイト増えており、ヘッダ用のチェックサムが追加されています。
 
   下記は初期文字列です。  
Initialization String
1         2      3       4       5       6       7       8       9       10
+-------+-------+-------+-------+-------+-------+-------+-------+-------+- -
| MAXL  | TIME  | NPAD  | PADC  | EOL   | QCTL  | QBIN  | CHKT  | REPT  |
+-------+-------+-------+-------+-------+-------+-------+-------+-------+- -
     10           CAPAS+1  CAPAS+2  CAPAS+3
- --+-------+ - -+--------+--------+--------+- -
    | CAPAS ... 0| WINDO  | MAXLX1 | MAXLX1 |
- --+-------+-  -+--------+--------+--------+- -

MAXL  Maximum length (0-94) +32
TIME  Timeout, seconds (0-94) +32
NPAD  Number of pad characters (0-94) +32
EOL   Packet terminator (0-63) +32
QCTL  Control prefix, literal
QBIN  8th bit prefix, literal
CHKT  Block check type {1,2,3}, literal
REPT  Repeat count prefix, literal
CAPAS Extendable capabilities mask, ends when value-32 is even
WINDO Window size (0-31) +32
MAXLX1
      High part of extended packet maximum length (int(max/95)+32)
MAXLX2
      Low part of extended packet maximum length (mod(max,95)+32)
 下記はパケット種別です。
Packet Types
Y   Acknowledgment (ACK). Data according to what kind of packet is being acknowledged.
N   Negative Acknowledgment (NAK). Data field always empty.
S   Send Initiation. Data field contains unencoded initialization string. Tells receiver to expect files. ACK to this packet also contains unencoded initialization string.
I   Initialize. Data field contains unencoded initialization string. Sent to server to set parameters prior to a command. ACK to this packet also contains unencoded initialization string.
F   File Header. Indicates file data about to arrive for named file. Data field contains encoded file name. ACK to this packet may contain encoded name receiver will store file under.
X   Text Header. Indicates screen data about to arrive. Data field contains encoded heading for display.
A   File Attributes. Data field contains unencoded attributes. ACK may contain unencoded corresponding
agreement or refusal, per attribute.
D   Data Packet. Data field contains encoded file or screen data. ACK may contain X to interrupt sending this file, Z to interrupt entire transaction.
Z   End of file. Data field may contain D for Discard.
B   Break transmission.
E   Error. Data field contains encoded error message.
R   Receive Initiate. Data field contains encoded file name.
C   Host Command. Data field contains encoded command for host's command processor.
K   Kermit Command. Data field contains encoded command for Kermit command processor.
T   Timeout psuedopacket, for internal use.
Q   Block check error psuedopacket, for internal use.
G   Generic Kermit Command. Data field contains a single character subcommand, followed by zero or more
    length-encoded operands, encoded after formation:
    I Login [<%user[%password[%account]]>]
    C CWD, Change Working Directory [<%directory[%password]>]
    L Logout, Bye
    F Finish (Shut down the server, but don't logout).
    D Directory [<%filespec>]
    U Disk Usage Query [<%area>]
    E Erase (delete) <%filespec>
    T Type <%filespec>
    R Rename <%oldname%newname>
    K Copy <%source%destination>
    W Who's logged in? [<%user ID or network host[%options]>]
    M Send a short Message <%destination%text>
    H Help [<%topic>]
    Q Server Status Query
    P Program <%[program-filespec][%program-commands]>
    J Journal <%command[%argument]>
    V Variable <%command[%argument[%argument]]>