created and maintained by tokd
SendmailTips
sendmail 覚書
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, ×tamp) != 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, ×tamp) != 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, ×tamp) != 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, ×tamp) != 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, ×tamp) != 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, ×tamp) != 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