created and maintained by tokd

SendmailTips

sendmail 覚書

aliases


使われていないエイリアスを削除するため、LogLevel を 11 にして
エイリアス展開のログを取るようにした。

DOUBLE BOUNCE メールとプライバシー


ダブルバウンスした(宛先不明で送信ホストに返ってきたけど、送信者も不明である)メールは
送信ホストの postmaster に配信されるが、その際に、メール本文がついてくる。
メール本文には postmaster にとって必要な情報はないはずだし、
プライバシー保護の観点からも、本文は削除したほうがよい。
 sed -n "19~1p" sendmail-8.XX.XX/contrib/mmuegel | sh 
して得られる src/postclip がそのためのプログラム。

ワイルドカード MX とウイルス(ワーム)


SMTP エンジンを持つウイルスにクライアントが感染しても、
外部にウイルスを送信しないように、
内部の DNS にはワイルドカード MX を使わないほうがよい。

access で CIDR 表記


/etc/mail/access では IP アドレスを使うことができるが、オクテット単位でしか
指定できない。sendmail-8.XX.XX/contrib/cidrexpand に前処理をさせると

66.109.25.0/25 REJECT

のような表記を展開してくれるので、記述が楽になる。

ユーザ名が数字のみの SPAM 対策


sendmail-8.XX.XX/cf/README に、送信者メールアドレスのユーザ名が数字のみの場合は
拒否するという設定がある。これを参考に、受信者メールアドレスのユーザ名が数字のみの
場合は拒否するようにした。sendmail が正規表現を扱えるように
sendmail-8.XX.XX/devtools/Site/site.config.m4 に
 define(`confMAPDEF', `-DMAP_REGEX')  を加えてコンパイルし、
config.mc に


LOCAL_CONFIG
Kallnumbers regex -a@MATCH ^[0-9]+$

LOCAL_RULESETS
SLocal_check_rcpt
# check address against various regex checks
R$*				$: $>Parse0 $>3 $1
R$+ < @ mydomain.com. > $*		$: $(allnumbers $1 $)
R@MATCH				$#error $: 553 Header Error

と追加する。

通常、メールアドレスは大文字小文字の区別は無いが、regex に -f オプションをつけて
マッチさせるパターンを記述し、ルールセット 3 を通さずにチェックすれば、
大文字小文字を区別して reject することが出来るみたい。

SMTP throttling による SPAM 対策

milter-7bit


RFC2045 に違反した transfer encoding ならば一定時間遅延するように変更。

milter-7bit-0.7 へのパッチ


*** milter-7bit.c.orig	Sat Jul 31 22:14:42 2004
--- milter-7bit.c	Fri Jan 21 18:22:58 2005
***************
*** 37,42 ****
--- 37,44 ----
  #define DEFAULT_HEADER_BUFFER_SIZE	8192
  #endif
  
+ #define WAIT_TIME			30
+ 
  /***********************************************************************
   *** No configuration below this point.
   ***********************************************************************/
***************
*** 601,607 ****
  		goto error5;
  	}
  
! 	(void) smfAccessHost(&data->work, MILTER_NAME "-connect:", client_name, client_addr, SMDB_ACCESS_OK);
  
  	return SMFIS_CONTINUE;
  error5:
--- 603,609 ----
  		goto error5;
  	}
  
! //	(void) smfAccessHost(&data->work, MILTER_NAME "-connect:", client_name, client_addr, SMDB_ACCESS_OK);
  
  	return SMFIS_CONTINUE;
  error5:
***************
*** 658,665 ****
  	data->transferEncoding = "7bit";
  	data->isTransferEncoding = is7bit;
  
! 	if (smfAccessMail(&data->work, MILTER_NAME "-from:", args[0], SMDB_ACCESS_UNKNOWN) == SMDB_ACCESS_ERROR)
! 		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
--- 660,667 ----
  	data->transferEncoding = "7bit";
  	data->isTransferEncoding = is7bit;
  
! //	if (smfAccessMail(&data->work, MILTER_NAME "-from:", args[0], SMDB_ACCESS_UNKNOWN) == SMDB_ACCESS_ERROR)
! //		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
***************
*** 675,682 ****
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "filterRcpt(%lx, %lx) RCPT='%s'", TAG_ARGS, (long) ctx, (long) args, args[0]);
  
! 	if (smfAccessRcpt(&data->work, MILTER_NAME "-to:", args[0]) == SMDB_ACCESS_ERROR)
! 		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
--- 677,684 ----
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "filterRcpt(%lx, %lx) RCPT='%s'", TAG_ARGS, (long) ctx, (long) args, args[0]);
  
! //	if (smfAccessRcpt(&data->work, MILTER_NAME "-to:", args[0]) == SMDB_ACCESS_ERROR)
! //		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
***************
*** 732,738 ****
  	mime = MimeHeader(data, name, value);
  
  	if (mime == MIME_ERROR) {
! 		return smfReply(&data->work, 550, "5.7.1", syntaxMessage);	
  	} else if (mime == MIME_CONTENT_TYPE) {
  		data->state = STATE_PART_CONTENT;
  	} else if (TextInsensitiveCompare(name, "Subject") == 0) {
--- 734,743 ----
  	mime = MimeHeader(data, name, value);
  
  	if (mime == MIME_ERROR) {
! //		return smfReply(&data->work, 550, "5.7.1", syntaxMessage);	
! 		syslog(LOG_INFO, TAG_FORMAT "%s, waiting %ds", TAG_ARGS, syntaxMessage, WAIT_TIME);
! 		sleep(WAIT_TIME);
! 		return SMFIS_CONTINUE;
  	} else if (mime == MIME_CONTENT_TYPE) {
  		data->state = STATE_PART_CONTENT;
  	} else if (TextInsensitiveCompare(name, "Subject") == 0) {
***************
*** 811,818 ****
  			if (smfLogDetail & SMF_LOG_DEBUG)
  				syslog(LOG_DEBUG, TAG_FORMAT "header=[%s] value=[%s]", TAG_ARGS, field, value);
  			
! 			if (MimeHeader(data, field, value) == MIME_ERROR)
! 				return smfReply(&data->work, 550, "5.7.1", syntaxMessage);	
  
  			continue;
  		}
--- 816,828 ----
  			if (smfLogDetail & SMF_LOG_DEBUG)
  				syslog(LOG_DEBUG, TAG_FORMAT "header=[%s] value=[%s]", TAG_ARGS, field, value);
  			
! //			if (MimeHeader(data, field, value) == MIME_ERROR)
! //				return smfReply(&data->work, 550, "5.7.1", syntaxMessage);	
! 			if (MimeHeader(data, field, value) == MIME_ERROR) {
! 				syslog(LOG_INFO, TAG_FORMAT "%s, waiting %ds", TAG_ARGS, syntaxMessage, WAIT_TIME);
! 				sleep(WAIT_TIME);
! 				return SMFIS_CONTINUE;
! 			}
  
  			continue;
  		}
***************
*** 871,878 ****
  	if (data->work.skipMessage)
  		goto skip0;
  
! 	if (smfLogDetail & SMF_LOG_INFO)
! 		syslog(LOG_INFO, TAG_FORMAT "pass=%s bytes=%lu", TAG_ARGS, isOK ? "YES" : "NO", data->byteCount);
  
  	if (!isOK) {
  		switch (action) {
--- 881,888 ----
  	if (data->work.skipMessage)
  		goto skip0;
  
! //	if (smfLogDetail & SMF_LOG_INFO)
! //		syslog(LOG_INFO, TAG_FORMAT "pass=%s bytes=%lu", TAG_ARGS, isOK ? "YES" : "NO", data->byteCount);
  
  	if (!isOK) {
  		switch (action) {
***************
*** 881,887 ****
  			goto skip0;
  		case SMFIS_REJECT:
  			if (PRECEDENCE_FIRST_CLASS <= data->precedence) {
! 				rc = smfReply(&data->work, 550, "5.7.1", rejectMessage);
  				goto skip0;
  			}
  			/*@fallthrough@*/
--- 891,900 ----
  			goto skip0;
  		case SMFIS_REJECT:
  			if (PRECEDENCE_FIRST_CLASS <= data->precedence) {
! //				rc = smfReply(&data->work, 550, "5.7.1", rejectMessage);
! 				syslog(LOG_INFO, TAG_FORMAT "%s, waiting %ds", TAG_ARGS, rejectMessage, WAIT_TIME);
! 				sleep(WAIT_TIME);
! 				rc = SMFIS_CONTINUE;
  				goto skip0;
  			}
  			/*@fallthrough@*/
***************
*** 928,937 ****
  	 */
  	length = snprintf(data->line, sizeof (data->line), MILTER_VERSION " (%s [%s]); ", if_name, if_addr);			
  	length += TimeStampAdd(data->line + length, sizeof (data->line) - length);
! 	(void) smfi_addheader(ctx, X_SCANNED_BY, data->line);
  
  	/* There must be only one of these and the last hop has final say. */
! 	(void) smfHeaderSet(ctx, X_MILTER_PASS, isOK ? "YES" : "NO", 1, data->hasPass);
  skip0:	
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "exit filterEndMessage(%lx)", TAG_ARGS, (long) ctx);
--- 941,950 ----
  	 */
  	length = snprintf(data->line, sizeof (data->line), MILTER_VERSION " (%s [%s]); ", if_name, if_addr);			
  	length += TimeStampAdd(data->line + length, sizeof (data->line) - length);
! //	(void) smfi_addheader(ctx, X_SCANNED_BY, data->line);
  
  	/* There must be only one of these and the last hop has final say. */
! //	(void) smfHeaderSet(ctx, X_MILTER_PASS, isOK ? "YES" : "NO", 1, data->hasPass);
  skip0:	
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "exit filterEndMessage(%lx)", TAG_ARGS, (long) ctx);

milter-date


ヘッダの時刻表記が RFC2822 に違反していたり、一定時間以上ずれていたら遅延させるように変更。

milter-date-0.12 へのパッチ


*** milter-date.c.orig	Sat Jul 31 22:16:03 2004
--- milter-date.c	Fri Jan 21 18:25:19 2005
***************
*** 70,75 ****
--- 70,77 ----
  #define DEFAULT_HOP_FUTURE		(2 * 3600)
  #endif 
  
+ #define WAIT_TIME			15
+ 
  /***********************************************************************
   *** No configuration below this point.
   ***********************************************************************/
***************
*** 224,230 ****
  		goto error1;
  	}
  
! 	(void) smfAccessHost(&data->work, MILTER_NAME "-connect:", client_name, client_addr, SMDB_ACCESS_OK);
  
  	return SMFIS_CONTINUE;
  error1:
--- 226,232 ----
  		goto error1;
  	}
  
! //	(void) smfAccessHost(&data->work, MILTER_NAME "-connect:", client_name, client_addr, SMDB_ACCESS_OK);
  
  	return SMFIS_CONTINUE;
  error1:
***************
*** 267,274 ****
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "filterMail(%lx, %lx) MAIL='%s'", TAG_ARGS, (long) ctx, (long) args, args[0]);
  
! 	if (smfAccessMail(&data->work, MILTER_NAME "-from:", args[0], SMDB_ACCESS_UNKNOWN) == SMDB_ACCESS_ERROR)
! 		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
--- 269,276 ----
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "filterMail(%lx, %lx) MAIL='%s'", TAG_ARGS, (long) ctx, (long) args, args[0]);
  
! //	if (smfAccessMail(&data->work, MILTER_NAME "-from:", args[0], SMDB_ACCESS_UNKNOWN) == SMDB_ACCESS_ERROR)
! //		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
***************
*** 284,291 ****
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "filterRcpt(%lx, %lx) RCPT='%s'", TAG_ARGS, (long) ctx, (long) args, args[0]);
  
! 	if (smfAccessRcpt(&data->work, MILTER_NAME "-to:", args[0]) == SMDB_ACCESS_ERROR)
! 		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
--- 286,293 ----
  	if (smfLogDetail & SMF_LOG_TRACE)
  		syslog(LOG_DEBUG, TAG_FORMAT "filterRcpt(%lx, %lx) RCPT='%s'", TAG_ARGS, (long) ctx, (long) args, args[0]);
  
! //	if (smfAccessRcpt(&data->work, MILTER_NAME "-to:", args[0]) == SMDB_ACCESS_ERROR)
! //		return SMFIS_REJECT;
  
  	return SMFIS_CONTINUE;
  }
***************
*** 954,961 ****
  	
  	return SMFIS_CONTINUE;
  illegal_syntax:
! 	(void) smfReply(&data->work, 550, "5.7.1", "\"%s\" does not conform to RFC 2822 section 3.3. Date and Time Specification", start);
! 	return SMFIS_REJECT;
  }
  
  static sfsistat
--- 956,966 ----
  	
  	return SMFIS_CONTINUE;
  illegal_syntax:
! //	(void) smfReply(&data->work, 550, "5.7.1", "\"%s\" does not conform to RFC 2822 section 3.3. Date and Time Specification", start);
! //	return SMFIS_REJECT;
! 	syslog(LOG_INFO, TAG_FORMAT "\"%s\" does not conform to RFC 2822 section 3.3. Date and Time Specification, waiting %ds", TAG_ARGS, start, WAIT_TIME);
! 	sleep(WAIT_TIME);
! 	return SMFIS_CONTINUE;
  }
  
  static sfsistat
***************
*** 996,1003 ****
  		
  		/* The ; date-time field are required in a Received: header. */
  		if (date[0] == '\0') {
! 			(void) smfReply(&data->work, 550, "5.7.1", "missing semi-colon date separator in a Received: header");
! 			return data->result = action;
  		}
  #else
  		/* Relaxed the parsing for a semicolon to find the last one
--- 1001,1011 ----
  		
  		/* The ; date-time field are required in a Received: header. */
  		if (date[0] == '\0') {
! //			(void) smfReply(&data->work, 550, "5.7.1", "missing semi-colon date separator in a Received: header");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "missing semi-colon date separator in a Received: header, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		}
  #else
  		/* Relaxed the parsing for a semicolon to find the last one
***************
*** 1038,1044 ****
  		 * section 3.3.
  		 */
  		if (convertDateToGMT(data, date, &timestamp) != SMFIS_CONTINUE)
! 			return data->result = action;
  
  		if (smfLogDetail & SMF_LOG_TRACE)
  			syslog(LOG_DEBUG, TAG_FORMAT "Received: header date=%s thisHop=%lu nextHop=%lu nextHop-thisHop=%ld", TAG_ARGS, date, timestamp, data->received, data->received - timestamp);
--- 1046,1053 ----
  		 * section 3.3.
  		 */
  		if (convertDateToGMT(data, date, &timestamp) != SMFIS_CONTINUE)
! //			return data->result = action;
! 			return data->result = SMFIS_CONTINUE;
  
  		if (smfLogDetail & SMF_LOG_TRACE)
  			syslog(LOG_DEBUG, TAG_FORMAT "Received: header date=%s thisHop=%lu nextHop=%lu nextHop-thisHop=%ld", TAG_ARGS, date, timestamp, data->received, data->received - timestamp);
***************
*** 1048,1058 ****
  		 * delivery window between hops.
  		 */
  		if (0 <= hopMaxTime && timestamp + hopMaxTime < data->received) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "time between hops too long");
! 			data->result = action;
  		} else if (0 <= hopMaxFuture && data->received + hopMaxFuture < timestamp) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "time travel between hops");
! 			data->result = action;
  		}
  		
  		data->received = timestamp;
--- 1057,1073 ----
  		 * delivery window between hops.
  		 */
  		if (0 <= hopMaxTime && timestamp + hopMaxTime < data->received) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "time between hops too long");
! //			data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "time between hops too long, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			data->result = SMFIS_CONTINUE;
  		} else if (0 <= hopMaxFuture && data->received + hopMaxFuture < timestamp) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "time travel between hops");
! //			data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "time travel between hops, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			data->result = SMFIS_CONTINUE;
  		}
  		
  		data->received = timestamp;
***************
*** 1061,1074 ****
  	} else if (TextInsensitiveCompare("Resent-Date", name) == 0) {
  		/* TEST: validate and convert date-time string. */
  		if (convertDateToGMT(data, value, &timestamp) != SMFIS_CONTINUE)
! 			return data->result = action;
  
  		/* TEST: When there are multiple Resent-Date: headers, they had better
  		 * reflect a consistant linear space-time continuum.
  		 */
  		if (data->hasResentDate && data->resent < timestamp) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "inconsistent Resent-Date headers");
! 			return data->result = action;
  		}
  
  		data->resent = timestamp;
--- 1076,1093 ----
  	} else if (TextInsensitiveCompare("Resent-Date", name) == 0) {
  		/* TEST: validate and convert date-time string. */
  		if (convertDateToGMT(data, value, &timestamp) != SMFIS_CONTINUE)
! //			return data->result = action;
! 			return data->result = SMFIS_CONTINUE;
  
  		/* TEST: When there are multiple Resent-Date: headers, they had better
  		 * reflect a consistant linear space-time continuum.
  		 */
  		if (data->hasResentDate && data->resent < timestamp) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "inconsistent Resent-Date headers");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "inconsistent Resent-Date headers, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		}
  
  		data->resent = timestamp;
***************
*** 1091,1125 ****
  
  		/* The Resent-Date: header must be within the allowed delivery window. */
  		if (0 <= dateMaxAge && timestamp + dateMaxAge < data->now) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "resent message too old");
! 			return data->result = action;
  		} else if (0 <= dateMaxFuture && data->now + dateMaxFuture < timestamp) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "resent message from the future, please check your computer's date, time, and time-zone");
! 			return data->result = action;
  		} else
  		
  		/* Compare last Received: header with the Resenrt-Date: header,
  		 * which must be within the allowed delivery window between hops.
  		 */
  		if (0 <= hopMaxTime && timestamp + hopMaxTime < data->received) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "resent message was sent too late");
! 			return data->result = action;
  		} else if (0 <= hopMaxFuture && data->received + hopMaxFuture < timestamp) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "resent message younger than first hop, please check your computer's date, time, and time-zone");
! 			return data->result = action;
  		}
  	} else if (TextInsensitiveCompare("Date", name) == 0) {
  		/* TEST that there is only one Date: header. */
  		if (data->hasDate) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "too many Date: headers");
! 			return data->result = action;
  		}		
  		
  		data->hasDate = 1;
  
  		/* TEST: validate and convert date-time string. */		
  		if (convertDateToGMT(data, value, &timestamp) != SMFIS_CONTINUE)
! 			return data->result = action;
  		
  		/* BY-PASS: When there are one or more Resent-Date: headers then
  		 * the Date: header can be dramatically "aged" or out-of-date.
--- 1110,1160 ----
  
  		/* The Resent-Date: header must be within the allowed delivery window. */
  		if (0 <= dateMaxAge && timestamp + dateMaxAge < data->now) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "resent message too old");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "resent message too old, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		} else if (0 <= dateMaxFuture && data->now + dateMaxFuture < timestamp) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "resent message from the future, please check your computer's date, time, and time-zone");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "resent message from the future, please check your computer's date, time, and time-zone, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		} else
  		
  		/* Compare last Received: header with the Resenrt-Date: header,
  		 * which must be within the allowed delivery window between hops.
  		 */
  		if (0 <= hopMaxTime && timestamp + hopMaxTime < data->received) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "resent message was sent too late");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "resent message was sent too late, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		} else if (0 <= hopMaxFuture && data->received + hopMaxFuture < timestamp) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "resent message younger than first hop, please check your computer's date, time, and time-zone");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "resent message younger than first hop, please check your computer's date, time, and time-zone, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		}
  	} else if (TextInsensitiveCompare("Date", name) == 0) {
  		/* TEST that there is only one Date: header. */
  		if (data->hasDate) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "too many Date: headers");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "too many Date: headers, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		}		
  		
  		data->hasDate = 1;
  
  		/* TEST: validate and convert date-time string. */		
  		if (convertDateToGMT(data, value, &timestamp) != SMFIS_CONTINUE)
! //			return data->result = action;
! 			return data->result = SMFIS_CONTINUE;
  		
  		/* BY-PASS: When there are one or more Resent-Date: headers then
  		 * the Date: header can be dramatically "aged" or out-of-date.
***************
*** 1137,1164 ****
  		 * delivery window.
  		 */
  		if (0 <= dateMaxAge && timestamp + dateMaxAge < data->now) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "message is too old");
! 			return data->result = action;
  		} else if (0 <= dateMaxFuture && data->now + dateMaxFuture < timestamp) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "message is from the future, please check your computer's date, time, and time-zone");
! 			return data->result = action;
  		} else
  		
  		/* Compare last Received: header with the Date: header, which
  		 * must be within the allowed delivery window between hops.
  		 */
  		if (0 <= hopMaxTime && timestamp + hopMaxTime < data->received) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "message was sent too late");
! 			return data->result = action;
  		} else if (0 <= hopMaxFuture && data->received + hopMaxFuture < timestamp) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "message younger than first hop, please check your computer's date, time, and time-zone");
! 			return data->result = action;
  		}
  	} else if (TextInsensitiveCompare("From", name) == 0) {
  		/* TEST that there is only one From: header. */
  		if (data->hasFrom) {
! 			(void) smfReply(&data->work, 550, "5.7.1", "too many From: headers");
! 			return data->result = action;
  		}		
  		
  		data->hasFrom = 1;
--- 1172,1214 ----
  		 * delivery window.
  		 */
  		if (0 <= dateMaxAge && timestamp + dateMaxAge < data->now) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "message is too old");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "message is too old, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		} else if (0 <= dateMaxFuture && data->now + dateMaxFuture < timestamp) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "message is from the future, please check your computer's date, time, and time-zone");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "message is from the future, please check your computer's date, time, and time-zone, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		} else
  		
  		/* Compare last Received: header with the Date: header, which
  		 * must be within the allowed delivery window between hops.
  		 */
  		if (0 <= hopMaxTime && timestamp + hopMaxTime < data->received) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "message was sent too late");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "message was sent too late, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		} else if (0 <= hopMaxFuture && data->received + hopMaxFuture < timestamp) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "message younger than first hop, please check your computer's date, time, and time-zone");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "message younger than first hop, please check your computer's date, time, and time-zone, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		}
  	} else if (TextInsensitiveCompare("From", name) == 0) {
  		/* TEST that there is only one From: header. */
  		if (data->hasFrom) {
! //			(void) smfReply(&data->work, 550, "5.7.1", "too many From: headers");
! //			return data->result = action;
! 			syslog(LOG_INFO, TAG_FORMAT "too many From: headers, waiting %ds", TAG_ARGS, WAIT_TIME);
! 			sleep(WAIT_TIME);
! 			return data->result = SMFIS_CONTINUE;
  		}		
  		
  		data->hasFrom = 1;
***************
*** 1183,1196 ****
  
  	/* TEST that there is a Date: header. */
  	if (!data->hasDate) {
! 		(void) smfReply(&data->work, 550, "5.7.1", "missing Date: header as required by RFC 2822 section 3.6");
! 		return action;
  	}
  	
  	/* TEST that there is a From: header. */
  	if (!data->hasFrom) {
! 		(void) smfReply(&data->work, 550, "5.7.1", "missing From: header as required by RFC 2822 section 3.6");
! 		return action;
  	}
  	
  	return SMFIS_CONTINUE;
--- 1233,1252 ----
  
  	/* TEST that there is a Date: header. */
  	if (!data->hasDate) {
! //		(void) smfReply(&data->work, 550, "5.7.1", "missing Date: header as required by RFC 2822 section 3.6");
! //		return action;
! 		syslog(LOG_INFO, TAG_FORMAT "missing Date: header as required by RFC 2822 section 3.6, waiting %ds", TAG_ARGS, WAIT_TIME);
! 		sleep(WAIT_TIME);
! 		return SMFIS_CONTINUE;
  	}
  	
  	/* TEST that there is a From: header. */
  	if (!data->hasFrom) {
! //		(void) smfReply(&data->work, 550, "5.7.1", "missing From: header as required by RFC 2822 section 3.6");
! //		return action;
! 		syslog(LOG_INFO, TAG_FORMAT "missing From: header as required by RFC 2822 section 3.6, waiting %ds", TAG_ARGS, WAIT_TIME);
! 		sleep(WAIT_TIME);
! 		return SMFIS_CONTINUE;
  	}
  	
  	return SMFIS_CONTINUE;
***************
*** 1199,1208 ****
  static int
  headerAddOrChange(SMFICTX *ctx, char *field, char *value, int index, int present)
  {
! 	if (present)
! 		return smfi_chgheader(ctx, field, index, value);
  		
! 	return smfi_addheader(ctx, field, value);
  }
  
  static sfsistat
--- 1255,1265 ----
  static int
  headerAddOrChange(SMFICTX *ctx, char *field, char *value, int index, int present)
  {
! //	if (present)
! //		return smfi_chgheader(ctx, field, index, value);
  		
! //	return smfi_addheader(ctx, field, value);
! 	return 0;
  }
  
  static sfsistat
***************
*** 1225,1231 ****
  
  	length = snprintf(data->line, sizeof (data->line), MILTER_VERSION " (%s [%s]); ", if_name, if_addr);			
  	length += TimeStampAdd(data->line + length, SMTP_LINE_SIZE - length);
! 	(void) smfi_addheader(ctx, X_SCANNED_BY, data->line);
  
  	isSpam = (data->work.replyLine[0] != '\0') || !data->hasDate || !data->hasFrom;
  
--- 1282,1288 ----
  
  	length = snprintf(data->line, sizeof (data->line), MILTER_VERSION " (%s [%s]); ", if_name, if_addr);			
  	length += TimeStampAdd(data->line + length, SMTP_LINE_SIZE - length);
! //	(void) smfi_addheader(ctx, X_SCANNED_BY, data->line);
  
  	isSpam = (data->work.replyLine[0] != '\0') || !data->hasDate || !data->hasFrom;
  
***************
*** 1240,1246 ****
  	/* There must be only one of these and the last hop has final say. */
  	(void) headerAddOrChange(ctx, X_MILTER_PASS, isSpam ? "NO" : "YES", 1, data->hasPass);
  
! 	syslog(LOG_INFO, TAG_FORMAT "pass=%s", TAG_ARGS, isSpam ? "NO" : "YES");
  
  	return SMFIS_CONTINUE;
  }
--- 1297,1303 ----
  	/* There must be only one of these and the last hop has final say. */
  	(void) headerAddOrChange(ctx, X_MILTER_PASS, isSpam ? "NO" : "YES", 1, data->hasPass);
  
! //	syslog(LOG_INFO, TAG_FORMAT "pass=%s", TAG_ARGS, isSpam ? "NO" : "YES");
  
  	return SMFIS_CONTINUE;
  }

milter-regex (Bounce SPAM と空 Message-Id メール対策)


正当なバウンスメール(内→外→内、外→内→外)は通過させ、envelope from が詐称
されたためにやってくる不当なバウンスメール(外1→外2→内) を遅延させるため、
以下のような仕組みを考案。

まず、ファイアウォールとなるサーバを通過するメールにバウンス検出用ヘッダを
付加する。正当なバウンスメールならばメール本文にバウンス検出用ヘッダを含むが、
不当なバウンスメールならばこのヘッダがない。このバウンス SPAM の検出は
milter-regex http://www.benzedrine.cx/milter-regex.html を利用する。

milter-regex-1.5 へのパッチ


*** milter-regex.c.orig	Wed Jan 19 17:04:55 2005
--- milter-regex.c	Tue Feb  8 13:34:35 2005
***************
*** 93,98 ****
--- 93,100 ----
  #define XCODE_TEMPFAIL	"4.7.1"
  #define	MAXRS		16
  
+ #define WAIT_TIME	30
+ 
  #if __linux__ || __sun__
  #define	ST_MTIME st_mtime
  extern size_t	 strlcpy(char *, const char *, size_t);
***************
*** 159,173 ****
  		    context->hdr_subject);
  		return (action->type);
  	}
! 	if (action->type == SMFIS_REJECT &&
! 	    smfi_setreply(ctx, RCODE_REJECT, XCODE_REJECT,
! 	    (char *)action->msg) != MI_SUCCESS)
! 		msg(LOG_ERR, context, "smfi_setreply");
  	if (action->type == SMFIS_TEMPFAIL &&
  	    smfi_setreply(ctx, RCODE_TEMPFAIL, XCODE_TEMPFAIL,
  	    (char *)action->msg) != MI_SUCCESS)
  		msg(LOG_ERR, context, "smfi_setreply");
! 	return (action->type);
  }
  
  static struct ruleset *
--- 161,183 ----
  		    context->hdr_subject);
  		return (action->type);
  	}
! //	if (action->type == SMFIS_REJECT &&
! //	    smfi_setreply(ctx, RCODE_REJECT, XCODE_REJECT,
! //	    (char *)action->msg) != MI_SUCCESS)
! //		msg(LOG_ERR, context, "smfi_setreply");
! 	if (action->type == SMFIS_REJECT) {
! 		char *qid;
! 		if ((qid = smfi_getsymval(ctx, "i")) == NULL)
! 			qid = "NOQUEUE";
! 		syslog(LOG_INFO, "%s: %s, waiting %ds", qid, (char *)action->msg, WAIT_TIME);
! 		sleep(WAIT_TIME);
! 	}
  	if (action->type == SMFIS_TEMPFAIL &&
  	    smfi_setreply(ctx, RCODE_TEMPFAIL, XCODE_TEMPFAIL,
  	    (char *)action->msg) != MI_SUCCESS)
  		msg(LOG_ERR, context, "smfi_setreply");
! //	return (action->type);
! 	return (SMFIS_CONTINUE);
  }
  
  static struct ruleset *
***************
*** 514,521 ****
  		msg[0] = 0;
  	vsnprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), fmt, ap);
  	if (debug)
! 		printf("syslog: %s\n", msg);
! 	else
  		syslog(priority, "%s", msg);
  	va_end(ap);
  }
--- 524,531 ----
  		msg[0] = 0;
  	vsnprintf(msg + strlen(msg), sizeof(msg) - strlen(msg), fmt, ap);
  	if (debug)
! //		printf("syslog: %s\n", msg);
! //	else
  		syslog(priority, "%s", msg);
  	va_end(ap);
  }
***************
*** 548,554 ****
  	const char *ofile = NULL;
  
  	tzset();
! 	openlog("milter-regex", LOG_PID | LOG_NDELAY, LOG_DAEMON);
  
  	while ((ch = getopt(argc, argv, "c:dp:u:")) != -1) {
  		switch (ch) {
--- 558,565 ----
  	const char *ofile = NULL;
  
  	tzset();
! //	openlog("milter-regex", LOG_PID | LOG_NDELAY, LOG_DAEMON);
! 	openlog("milter-regex", LOG_PID | LOG_NDELAY, LOG_MAIL);
  
  	while ((ch = getopt(argc, argv, "c:dp:u:")) != -1) {
  		switch (ch) {

sendmail.cf への追加(milter 関連は省略)


HX-Bounce-Checker: example.com

milter-regex.conf の設定


reject "Bounce SPAM"
envfrom /<>/ and not body /^X-Bounce-Checker: example\.com$/

reject "Null Message-Id"
not header /^message-id$/i //

メールボディを検査するのでサーバへの負荷は増加する。

milter-sender


milter-sender http://www.milter.info/milter-sender/ を SMTP throttling 用
フィルタとして利用する。方針としては
- こちらからは一切 reject しない(以下のときに SMTP 応答を遅延させるだけ)
- 逆引きと正引きが一致しない(forged)クライアントならば SMTP 応答を遅らせる
- 逆引きできないクライアントならば SMTP 応答を遅らせる

milter-sender-0.62 へのパッチ


*** milter-sender.c.orig	Wed Jan 19 11:06:51 2005
--- milter-sender.c	Fri Jan 21 17:29:57 2005
***************
*** 1074,1085 ****
  	 * {client_resolve} equals one of OK, TEMP, FAIL, or FORGED.
  	 */
  	if (clientIsForged.value && client_resolve[1] == 'O') {
! 		rc = setReply(data, 550, NULL, "connection [%s] is forged (PTR and A records mismatch)", client_addr);
  		goto error0;
  	}
  
  	if (clientNeedsPTR.value && client_resolve[0] != 'O' && client_resolve[1] != 'O') {
! 		rc = setReply(data, client_resolve[0] == 'T' ? 450 : 550, NULL, "connection [%s] failed to resolve", client_addr);
  		goto error0;
  	}
  
--- 1074,1091 ----
  	 * {client_resolve} equals one of OK, TEMP, FAIL, or FORGED.
  	 */
  	if (clientIsForged.value && client_resolve[1] == 'O') {
! //		rc = setReply(data, 550, NULL, "connection [%s] is forged (PTR and A records mismatch)", client_addr);
! 		syslog(LOG_INFO, TAG_FORMAT "connection [%s] is forged, waiting %lds", TAG_ARGS, client_addr, socketTimeout.value/1000);
! 		sleep(socketTimeout.value/1000);
! 		rc = SMFIS_CONTINUE;
  		goto error0;
  	}
  
  	if (clientNeedsPTR.value && client_resolve[0] != 'O' && client_resolve[1] != 'O') {
! //		rc = setReply(data, client_resolve[0] == 'T' ? 450 : 550, NULL, "connection [%s] failed to resolve", client_addr);
! 		syslog(LOG_INFO, TAG_FORMAT "connection [%s] failed to resolve, waiting %lds", TAG_ARGS, client_addr, socketTimeout.value*2/1000);	
! 		sleep(socketTimeout.value*2/1000);
! 		rc = SMFIS_CONTINUE;
  		goto error0;
  	}

milter-sender.cf(関連する変更点のみ)


*** milter-sender.cf.sample	Tue Jan 18 19:29:50 2005
--- milter-sender.cf	Thu Jan 20 20:33:05 2005
***************
*** 33,42 ****
  CacheRejectTTL=0
  #   cache time-to-live in seconds for rejected senders, 0 = disable
  
! ClientIsForged=0
  #   reject connection when PTR and A records do not match
  
! ClientNeedsPTR=0
  #   reject connection when no PTR record is found
  
  ClientRejectBenchmark=1
--- 33,42 ----
  CacheRejectTTL=0
  #   cache time-to-live in seconds for rejected senders, 0 = disable
  
! ClientIsForged=1
  #   reject connection when PTR and A records do not match
  
! ClientNeedsPTR=1
  #   reject connection when no PTR record is found
  
  ClientRejectBenchmark=1
***************
*** 66,72 ****
  ClientRejectThisNet=1
  #   reject connection from RFC 3330 "this" network 0.0.0.0/8
  
! DebugLogOnly=0
  #   debug mode logs messages, never rejects
  
  DeferHeloReject=0
--- 66,72 ----
  ClientRejectThisNet=1
  #   reject connection from RFC 3330 "this" network 0.0.0.0/8
  
! DebugLogOnly=1
  #   debug mode logs messages, never rejects
  
  DeferHeloReject=0
***************
*** 142,154 ****
  MxCallAhead=0
  #   an MX gateway can call the next hop to verify the recipient
  
! MxCallBackConnect=1
  #   enable the callback SMTP connection
  
  MxCallBackIpBlocked=0
  #   if our IP appears in an callback error response, assume accepts any email
  
! MxCallBackMaxAttempts=3
  #   maximum number of MX hosts to attempt callback with, 0 = disable callback
  
  MxRejectBenchmark=1
--- 142,154 ----
  MxCallAhead=0
  #   an MX gateway can call the next hop to verify the recipient
  
! MxCallBackConnect=0
  #   enable the callback SMTP connection
  
  MxCallBackIpBlocked=0
  #   if our IP appears in an callback error response, assume accepts any email
  
! MxCallBackMaxAttempts=0
  #   maximum number of MX hosts to attempt callback with, 0 = disable callback
  
  MxRejectBenchmark=1
***************
*** 187,193 ****
  SkipLoopbackInterface=1
  #   skip the callback if the connection is from the loopback interface
  
! SocketTimeout=120
  #   SMTP response timeout in seconds, 0 = indefinite
  
  TlsRejectIfMissing=0
--- 187,193 ----
  SkipLoopbackInterface=1
  #   skip the callback if the connection is from the loopback interface
  
! SocketTimeout=15
  #   SMTP response timeout in seconds, 0 = indefinite
  
  TlsRejectIfMissing=0