C言語でprefork型のデーモンを書く(4): init スクリプト

prefork して、シャットダウン可能なデーモン本体ができたので、次はinit スクリプトを用意する。
適当なサンプルは、/etc/init.d/ 以下にたくさんあるのでそれを参考に書いてみた。
デーモン起動時に /var/run/my_prefork_daemon.pid が自動作成されるのを利用して、起動中かどうかを判断し重複起動の防止を行い、シャットダウン時には .pid ファイルを削除している。

以下のスクリプトをchmod 755して、/etc/init.d/my_prefork_daemon として保存する。 Cで書いた my_prefork_daemon 本体へのパスは要変更。
75,25 という数字は、起動、停止 順序を決める数字(75が起動、25が停止)。前提となるサービスがある場合には、そのサービスの後に起動され、そのサービスの前に停止するように、数字を大きく/小さくする必要がある。

#! /bin/bash
#
# my_prefork_daemon
#
# chkconfig: 2345 75 25
# description: foo bar
#
# Source function library.
. /etc/rc.d/init.d/functions 
  
prog="my_prefork_daemon"

RETVAL=0

start() {
	echo -n $"Starting $prog: "	
        if [ -e /var/run/$prog.pid ]; then
		failure
		echo
		echo "cannot start $prog: $prog is already running.";
		return 1
	fi
	daemon /PATH/TO/YOUR/$prog && success
	RETVAL=$?
	echo
	return $RETVAL
}

stop() {
	echo -n $"Stopping $prog: "
        if [ ! -e /var/run/$prog.pid ]; then
		failure
		echo
	 	echo $"cannot stop $prog: $prog is not running."
		return 1;
	fi
	kill `cat /var/run/$prog.pid` && success
	RETVAL=$?
	echo
        [ $RETVAL -eq 0 ] && rm -f /var/run/$prog.pid
	return $RETVAL
}	

restart() {
  	stop
	start
}	

case "$1" in
  start)
  	start
	;;
  stop)
  	stop
	;;
  restart)
  	restart
	;;
  status)
	if [ -f /var/run/$prog.pid ]; then
		echo $"$prog is running."
		RETVAL=0
	else
		echo $"$prog is not running."
		RETVAL=3
	fi
	;;
  *)
	echo $"Usage: $0 {start|stop|restart|status}"
	exit 1
esac

exit 0
  • chkconfig

init スクリプトができたら、chkconfig で自動起動の設定。

サービスに追加
# chkconfig --add my_prefork_daemon

自動起動の設定
# chkconfig my_prefork_daemon on

サービス一覧表示
# chkconfig --list
〜前略〜
crond          	0:off	1:off	2:on	3:on	4:on	5:on	6:off
my_prefork_daemon	0:off	1:off	2:on	3:on	4:on	5:on	6:off
network        	0:off	1:off	2:on	3:on	4:on	5:on	6:off
postfix        	0:off	1:off	2:on	3:on	4:on	5:on	6:off
sshd           	0:off	1:off	2:on	3:on	4:on	5:on	6:off
〜後略〜
  • service コマンドで起動、停止のテスト
# service  my_prefork_daemon start
Starting my_prefork_daemon:                                [  OK  ]

# service  my_prefork_daemon restart
Stopping my_prefork_daemon:                                [  OK  ]
Starting my_prefork_daemon:                                [  OK  ]

# service  my_prefork_daemon stop
Stopping my_prefork_daemon:                                [  OK  ]

# service  my_prefork_daemon status
my_prefork_daemon is not running.

これでマシン起動時に自動で my_prefork_daemon が起動され、マシン停止時には自動でシャットダウンされる。


参考
C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル - Sleepless geek in Seattle
C言語でprefork型のデーモンを書く(2): 非デーモン prefork シグナルハンドラ付き - Sleepless geek in Seattle
C言語でprefork型のデーモンを書く(3): デーモン化 - Sleepless geek in Seattle

C言語でprefork型のデーモンを書く(3): デーモン化

prefork して、シグナルで綺麗に終了できるようになったので次はデーモン化する。デーモンをkill しやすいように プロセスIDをファイルに書いておくwrite_pid()関数と、デーモン化関数daemonize()を追加している。

デーモン化の処理が入ったバージョンのソース

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <apr_hash.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

#define MAX_CHILDREN 16 //子プロセスの数
#define PID_FILE "/var/run/my_prefork_daemon.pid"

void kill_all_children(int);
void signal_handler(int);
void write_pid();
void daemonize();

// 子プロセスの管理にハッシュテーブルを使う
static apr_pool_t* pool;
static apr_hash_t* hChildren;

int main(int argc, char **argv){

  // デーモン化
  daemonize();

  //kill しやすいように pid ファイルの作成
  write_pid();

  // SIGTERM ですべての子プロセスを殺すようにシグナルハンドラを設定
  signal(SIGTERM, kill_all_children);

  // ハッシュの初期化
  apr_initialize();
  apr_pool_create(&pool, NULL);
  hChildren = apr_hash_make(pool);
  
  //親プロセスのループ
  while(1){
	while( apr_hash_count(hChildren) >= MAX_CHILDREN ){
	  int status;
	  pid_t child_pid = wait( &status ); //子プロセスが死ぬまで待つ
	  apr_hash_set(hChildren, &child_pid, sizeof(child_pid), NULL); //死んだ子プロセスをハッシュテーブルから削除
	}

	pid_t *pid = apr_palloc(pool, sizeof(pid_t));
	*pid = fork(); //フォーク
	if(*pid==0){
	  signal(SIGTERM, signal_handler);
	  goto CHILDREN; //子プロセスだったら、ループから抜ける
	}
	apr_hash_set(hChildren, pid, sizeof(pid_t), 1); //子プロセスをハッシュテーブルに追加
	usleep(100);
  }
  
 CHILDREN:
  while(1){
	//子プロセスの処理をここに書く
	sleep(1);
  }
}

void kill_all_children(int sig){
  apr_hash_index_t *hi;
  apr_ssize_t klen;
  pid_t *child_pid_ptr;
  int val;
  for( hi=apr_hash_first(pool, hChildren); hi; hi=apr_hash_next(hi) ){
	apr_hash_this(hi, &child_pid_ptr, &klen, &val);
	int ret = kill(*child_pid_ptr, SIGTERM);
  }
  exit(0);
}

void signal_handler(int sig){
  exit(0);
}

void write_pid(){
  FILE *fp;
  fp = fopen(PID_FILE, "w");
  fprintf(fp, "%d", getpid());
  fclose(fp);
}

void daemonize(){
  pid_t pid, sid;
  
  /* カレントディレクトリを / に変更 */
  if( chdir("/") < 0 )	exit(EXIT_FAILURE);
  
  /* 子プロセスの開始 */
  pid = fork();
  if( pid < 0 ) exit(EXIT_FAILURE);
  
  /* 親プロセスを終了 */
  if( pid > 0 ) exit(EXIT_SUCCESS);
  
  /* 新しいセッションを作成 */
  sid = setsid();
  if( sid < 0 ) exit(EXIT_FAILURE);
  
  /* ファイル作成マスクをリセット */
  umask(0);
  
  /* 標準入力,標準出力,標準エラー出力を閉じる */
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);
  
  return;
}

細かいところはいい加減なので、警告がやたらでるので本番で使うときは -Wall を付けて警告がなくなるまで要修正。

# gcc -g -I /usr/include/apr-1 -L/usr/lib/apr-1 -lapr-1 my_prefork_daemon.c -o my_prefork_daemon
  • 実行

デーモン化されたため実行するとすぐにプロンプトが返ってくる。また、psしてみると、親プロセスの親プロセスIDが、シェルのプロセスIDではなく、「1」(initのプロセスID)になっているはず。

# ./my_prefork_daemon
# ps -ef|grep my_prefork_daemon
root      8529     1  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8530  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8531  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8532  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8533  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8534  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8535  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8536  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8537  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8538  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8539  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8540  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8541  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8542  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8543  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8544  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
root      8545  8529  0 23:40 ?        00:00:00 ./my_prefork_daemon
503       8547  4614  0 23:41 pts/3    00:00:00 grep my_prefork_daemon
  • プロセスの終了方法

親プロセスに対してシグナルを送信すれば終了できる。デーモンらしくプロセスIDを /var/run/my_prefork_daemon.pid というファイル名で残すようにしたので、シグナルの送信方法は、以下のようにすればOK。今後、/etc/init.d/ の下に起動/終了スクリプトを書くときにも使える。

# kill `cat /var/run/my_prefork_daemon.pid`

参考
C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル - Sleepless geek in Seattle
C言語でprefork型のデーモンを書く(2): 非デーモン prefork シグナルハンドラ付き - Sleepless geek in Seattle

C言語でprefork型のデーモンを書く(2): 非デーモン prefork シグナルハンドラ付き

1つの親プロセスとたくさんの子プロセスという構成。親プロセスに SIGTERM を送ると、すべての子プロセスをきれいに終了させた後で終了するようにシグナルハンドラを追加したサンプル。

  • my_prefork_signal.c

シグナルハンドラ付きのソース

#include <stdio.h>
#include <string.h>
#include <apr_hash.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

#define MAX_CHILDREN 16 //子プロセスの数

void kill_all_children(int);
void signal_handler(int);

// 子プロセスの管理にハッシュテーブルを使う
static apr_pool_t* pool;
static apr_hash_t* hChildren;

int main(void){

  // SIGTERM ですべての子プロセスを殺すようにシグナルハンドラを設定
  signal(SIGTERM, kill_all_children);

  // ハッシュの初期化
  apr_initialize();
  apr_pool_create(&pool, NULL);
  hChildren = apr_hash_make(pool);
  
  //親プロセスのループ
  while(1){
	while( apr_hash_count(hChildren) >= MAX_CHILDREN ){
	  int status;
	  pid_t child_pid = wait( &status ); //子プロセスが死ぬまで待つ
	  apr_hash_set(hChildren, &child_pid, sizeof(child_pid), NULL); //死んだ子プロセスをハッシュテーブルから削除
	}

	pid_t *pid = apr_palloc(pool, sizeof(pid_t));
	*pid = fork(); //フォーク
	if(*pid==0){
	  signal(SIGTERM, signal_handler);
	  goto CHILDREN; //子プロセスだったら、ループから抜ける
	}
	apr_hash_set(hChildren, pid, sizeof(pid_t), 1); //子プロセスをハッシュテーブルに追加
	usleep(100);
  }
  
 CHILDREN:
  while(1){
	//子プロセスの処理をここに書く
	sleep(1);
  }
}

void kill_all_children(int sig){
  apr_hash_index_t *hi;
  apr_ssize_t klen;
  pid_t *child_pid_ptr;
  int val;
  for( hi=apr_hash_first(pool, hChildren); hi; hi=apr_hash_next(hi) ){
	apr_hash_this(hi, &child_pid_ptr, &klen, &val);
	int ret = kill(*child_pid_ptr, SIGTERM);
  }
  exit(0);
}

void signal_handler(int sig){
  exit(0);
}

細かいところはいい加減なので、警告がやたらでるので本番で使うときは -Wall を付けて警告がなくなるまで要修正。

# gcc -g -I /usr/include/apr-1 -L/usr/lib/apr-1 -lapr-1 my_prefork_signal.c -o my_prefork_signal
  • 実行
$ ./my_prefork_signal &
[1] 8502
$ ps -ef|grep my_prefork_signal
503       8502  4614  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8503  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8504  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8505  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8506  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8507  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8508  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8509  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8510  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8511  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8512  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8513  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8514  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8515  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8516  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8517  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8518  8502  0 23:32 pts/3    00:00:00 ./my_prefork_signal
503       8521  4614  0 23:33 pts/3    00:00:00 grep my_prefork_signal
  • プロセスの終了方法

親プロセスに対してシグナルを送信すれば終了できる。
シグナルの送信方法は、

親プロセスのプロセスIDを調べる
# ps -ef

そのプロセスIDにSIGTERMを送る。
# kill 8502

参考
C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル - Sleepless geek in Seattle

C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル

Cで書かれた prefork デーモン(daemon)のちょうど良いサンプルが見つからなかったので自分で書く。
ちょうど良いお手本がないので PerlのソースをCに移植した。
プラットフォームはCentOS5.2。

  • my_prefork.c

ただの prefork のサンプル(デーモン化はしていない)

#include <stdio.h>
#include <string.h>
#include <apr_hash.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX_CHILDREN 16 //子プロセスの数

int main(void){
  // 子プロセスの管理にハッシュテーブルを使う
  apr_pool_t* pool;
  apr_hash_t* hChildren;
  apr_initialize();
  apr_pool_create(&pool, NULL);
  hChildren = apr_hash_make(pool);
  
  //親プロセスのループ
  while(1){
	while( apr_hash_count(hChildren) >= MAX_CHILDREN ){
	  int status;
	  pid_t child_pid = wait( &status ); //子プロセスが死ぬまで待つ
	  apr_hash_set(hChildren, &child_pid, sizeof(child_pid), NULL); //死んだ子プロセスをハッシュテーブルから削除
	}

	pid_t *pid = apr_palloc(pool, sizeof(pid_t));
	*pid = fork(); //フォーク
	if(*pid==0) goto CHILDREN; //子プロセスだったら、ループから抜ける
	apr_hash_set(hChildren, pid, sizeof(pid_t), 1); //子プロセスをハッシュテーブルに追加
	usleep(100);
  }
  
 CHILDREN:
  while(1){
	//子プロセスの処理をここに書く
	sleep(1);
  }
}
# gcc -g -I /usr/include/apr-1 -L/usr/lib/apr-1 -lapr-1 my_prefork.c -o my_prefork

Cでハッシュテーブルを使うときのメモ

Cでハッシュテーブルを使いたくなったので、調べてみたら APR(Apache Portability Runtime) のハッシュテーブルのパフォーマンスが良いらしい。名前的に移植性も良さそうな気がする。
簡単な使い方を兼ねたサンプルをメモがわりに残す。プラットフォームはCentOS5.2。

#include <stdio.h>
#include <string.h>
#include <apr_hash.h>

#define MAX_KEY_LENGTH 512
#define MAX_VAL_LENGTH 512

static apr_pool_t* pool;
static apr_hash_t* hash;

main(){
  //おまじない
  apr_initialize();
  apr_pool_create(&pool, NULL);
  hash = apr_hash_make(pool);

  //ハッシュに値をセット
  char *key, *val;
  int i;
  for(i=0; i<20; i++){
	key = apr_palloc(pool, MAX_KEY_LENGTH);
	val = apr_palloc(pool, MAX_VAL_LENGTH);
	sprintf(key, "KEY_%d", i);
	sprintf(val, "VAL_%d", i);
	apr_hash_set(hash, key, APR_HASH_KEY_STRING, val);
  }

  //キーを指定して値を取り出す
  val = apr_hash_get(hash, key, APR_HASH_KEY_STRING);
  printf("key=%s, val=%s\n\n", key,val);

  //ハッシュに入っているキーと値をすべて取り出す
  apr_hash_index_t *hi;
  apr_ssize_t klen;
  for( hi=apr_hash_first(pool, hash); hi; hi=apr_hash_next(hi) ){
	apr_hash_this(hi, (const void **)&key, &klen,(void **)&val);
	printf("key=%s, val=%s,  key_length=%d\n", key,val,(int)klen);
  }
}
# gcc -g -I /usr/include/apr-1 -L/usr/lib/apr-1 -lapr-1 my_hash.c -o my_hash
  • 実行
# ./my_hash
key=KEY_19, val=VAL_19

key=KEY_8, val=VAL_8,  key_length=5
key=KEY_9, val=VAL_9,  key_length=5
key=KEY_10, val=VAL_10,  key_length=6
key=KEY_11, val=VAL_11,  key_length=6
key=KEY_12, val=VAL_12,  key_length=6
key=KEY_13, val=VAL_13,  key_length=6
key=KEY_14, val=VAL_14,  key_length=6
key=KEY_15, val=VAL_15,  key_length=6
key=KEY_16, val=VAL_16,  key_length=6
key=KEY_17, val=VAL_17,  key_length=6
key=KEY_18, val=VAL_18,  key_length=6
key=KEY_19, val=VAL_19,  key_length=6
key=KEY_0, val=VAL_0,  key_length=5
key=KEY_1, val=VAL_1,  key_length=5
key=KEY_2, val=VAL_2,  key_length=5
key=KEY_3, val=VAL_3,  key_length=5
key=KEY_4, val=VAL_4,  key_length=5
key=KEY_5, val=VAL_5,  key_length=5
key=KEY_6, val=VAL_6,  key_length=5
key=KEY_7, val=VAL_7,  key_length=5

意外と簡単。

追加でglibc版も試した。どうやらiteration の機能が無いみたいなので注意が必要。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define __USE_GNU
#include <search.h>

#define MAX_KEY_LENGTH 512
#define MAX_VAL_LENGTH 512

main(void) {
  int ret;
  struct hsearch_data tab;
  
  /* おまじない */
  memset(&tab, 0, sizeof(tab));
  ret = hcreate_r(512, &tab);
  
  ENTRY e;
  ENTRY* search_result;

  /* ハッシュに値をセット */
  int i;
  for(i=0; i < 20; i++) {
	e.key = (char*)malloc(MAX_KEY_LENGTH);
	e.data = (char*)malloc(MAX_VAL_LENGTH);
	sprintf(e.key, "KEY_%d", i);
	sprintf(e.data, "VAL_%d", i);
    ret = hsearch_r(e, ENTER, &search_result, &tab);
  }

  /* キーを指定して値を取り出す */
  ret = hsearch_r(e, FIND, &search_result, &tab);
  printf("key=%s, val=%s\n", search_result->key, search_result->data);

  /* 全データを取り出す  */

  /* 全データをfree  */
  
}


参考
C/C++ で使える Hashtable - BOOLEANLABEL
hcreate_rなどを使ってみた - Limitの日記

Windows Server {2000,2003,2008} でDSRを行う方法

Keepalived + LVS + CentOS4 でロードバランサー(DSR) - Sleepless geek in Seattle」あたりで、Keepalived を使ったCentOS のロードバランスは簡単にできるようになったが、Windows Server の設定はどうやるんだろうと調べてみたら、こんな素敵なエントリ を見つけた。
Windows Server 2008 上にこの通りの設定をやってみたがうまく行かない。どうやら、上記のエントリでは、Windows Server 2003 でのみ動作するようだ。 Windows Server 2000, 2003, 2008 でやり方がそれぞれ違うことが分かったのでここにログを残そう。

  • loopback インタフェースの追加(2000, 2003, 2008 共通)

概要を書くと、

新しいデバイスの追加 -> ネットワーク アダプタ -> 製造元からMicrosoftを選択 -> ネットワーク アダプタの中から Microsoft Loopback Adapter を選択

そのloopback インタフェースに、Virtual IP を振る。
詳細は、「Windows serverでDSRを行う方法: sanonosa システム管理コラム集」 を参照。

Metric を254などの大きい値にする。方法は、「@IT:Windows TIPS -- Tips:高速なネットワーク・インターフェイスを自動的に選択可能にする」を参照。これだけ。

ファイアウォールを無効にするか、そのポートのトラフィックを遮断しないように設定する。これだけ。

ファイアウォールを無効にするか、そのポートのトラフィックを遮断しないように設定するのに加えて、以下のコマンドを実行する。
"Local Area Connection" と "loopback" の部分は、各自読み替えること。(Windows Server日本語版だと、 「ローカルエリア接続」 みたいな名前になる。)

netsh interface ipv4 set interface "Local Area Connection" weakhostreceive=enabled
netsh interface ipv4 set interface "loopback" weakhostreceive=enabled
netsh interface ipv4 set interface "loopback" weakhostsend=enabled


参考にしたサイト
Windows serverでDSRを行う方法: sanonosa システム管理コラム集
Loadbalancer.org Blog » Blog Archive » Direct Routing aka. Direct Server Return on Windows 2008 using loopback adpter

Postfixのコンテンツフィルターを複数設定する方法

Postfixには、ウィルススキャン や アンチSPAM などをフィルターコンテンツフィルターという方法でプラグインできる。
その際に、amavisd-newや自分で作ったカスタムフィルターなどを組み合わせたいときには、複数のコンテンツフィルターを併せて設定すればよい。
カスタムのコンテンツフィルターを追加したいときには、Perlで書かれたトランスペアレントなコンテンツフィルター smtpprox を使うと便利。
今回はsmtpprox を2つ設定した場合の設定手順のログ。

  • 最終的には以下のような感じ。
+-----------+  +----------------+  +-------------+  
|Postfix:25 |=>|my_filter1:10024|=>|Postfix:10025|=>
+-----------+  +----------------+  +-------------+  

+----------------+  +-------------+  
|my_filter2:20024|=>|Postfix:20025|====>
+----------------+  +-------------+  
  • smtpprox のインストール & 起動
# wget http://bent.latency.net/smtpprox/smtpprox-1.2.tar.gz
# tar zxvf smtpprox-1.2.tar.gz
# cd smtpprox-1.2

### my_filter1 ###
# ./smtpprox localhost:10024 localhost:10025

### my_filter2 ###
# ./smtpprox localhost:20024 localhost:20025

master.cf を以下のように修正

smtp      inet  n       -       n       -       -       smtpd

smtp      inet  n       -       n       -       -       smtpd
        -o content_filter=my_filter1:[127.0.0.1]:10024

のようにする。つまりコンテンツフィルタ my_filter1 を使う、という設定を追加する。


my_filter1 を設定(定義)する。

my_filter1 unix -   -   n   -   30  smtp
    -o smtp_data_done_timeout=1200
    -o smtp_send_xforward_command=yes
    -o disable_dns_lookups=yes

次に、my_filter1 から ポート10025に戻ってくる smtpd の設定を追加する。
その時に、2番目のコンテンツフィルタ my_filter2 を使う設定を行う。(2行目)

127.0.0.1:10025 inet n   -   n   -   -  smtpd
    -o content_filter=my_filter2:[127.0.0.1]:20024
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_client_restrictions=
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o mynetworks=127.0.0.0/8
    -o strict_rfc821_envelopes=yes
    -o smtpd_error_sleep_time=0
    -o smtpd_soft_error_limit=1001
    -o smtpd_hard_error_limit=1000
    -o smtpd_client_connection_count_limit=0
    -o smtpd_client_connection_rate_limit=0
    -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks

先ほどと同様に、my_filter2 の定義を追加する。

my_filter2 unix -   -   n   -   30  smtp
    -o smtp_data_done_timeout=1200
    -o smtp_send_xforward_command=yes
    -o disable_dns_lookups=yes

次に、my_filter2 から ポート20025に戻ってくる smtpd の設定を追加する。
その時に、コンテンツフィルタを空にしておく。(コンテンツフィルタは使わないので。)間違って指定するとループするかも。

127.0.0.1:20025 inet n   -   n   -   -  smtpd
    -o content_filter=
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_client_restrictions=
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o mynetworks=127.0.0.0/8
    -o strict_rfc821_envelopes=yes
    -o smtpd_error_sleep_time=0
    -o smtpd_soft_error_limit=1001
    -o smtpd_hard_error_limit=1000
    -o smtpd_client_connection_count_limit=0
    -o smtpd_client_connection_rate_limit=0
    -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks


最後は、以下のようになる。

smtp      inet  n       -       n       -       -       smtpd
        -o content_filter=my_filter1:[127.0.0.1]:10024
my_filter1 unix -   -   n   -   30  smtp
    -o smtp_data_done_timeout=1200
    -o smtp_send_xforward_command=yes
    -o disable_dns_lookups=yes
127.0.0.1:10025 inet n   -   n   -   -  smtpd
    -o content_filter=my_filter2:[127.0.0.1]:20024
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_client_restrictions=
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o mynetworks=127.0.0.0/8
    -o strict_rfc821_envelopes=yes
    -o smtpd_error_sleep_time=0
    -o smtpd_soft_error_limit=1001
    -o smtpd_hard_error_limit=1000
    -o smtpd_client_connection_count_limit=0
    -o smtpd_client_connection_rate_limit=0
    -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
my_filter2 unix -   -   n   -   30  smtp
    -o smtp_data_done_timeout=1200
    -o smtp_send_xforward_command=yes
    -o disable_dns_lookups=yes
127.0.0.1:20025 inet n   -   n   -   -  smtpd
    -o content_filter=
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_client_restrictions=
    -o smtpd_client_restrictions=
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o mynetworks=127.0.0.0/8
    -o strict_rfc821_envelopes=yes
    -o smtpd_error_sleep_time=0
    -o smtpd_soft_error_limit=1001
    -o smtpd_hard_error_limit=1000
    -o smtpd_client_connection_count_limit=0
    -o smtpd_client_connection_rate_limit=0
    -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks

以下略

これで、コンテンツフィルタの中で、メールの中身を煮るなり焼くなりできる。