Index: head/mail/p5-Mail-SpamAssassin/Makefile =================================================================== --- head/mail/p5-Mail-SpamAssassin/Makefile (revision 288061) +++ head/mail/p5-Mail-SpamAssassin/Makefile (revision 288062) @@ -1,320 +1,320 @@ # New ports collection makefile for: p5-Mail-SpamAssassin # Date created: Nov 26 2001 # Whom: Anthony Kim # # $FreeBSD$ # PORTNAME= Mail-SpamAssassin PORTVERSION= 3.3.2 -PORTREVISION= 3 +PORTREVISION= 4 CATEGORIES= mail perl5 MASTER_SITES= ${MASTER_SITE_APACHE:S/$/:apache/} ${MASTER_SITE_PERL_CPAN:S/$/:cpan/} MASTER_SITE_SUBDIR= spamassassin/source/:apache Mail/:cpan PKGNAMEPREFIX?= p5- DISTFILES= ${DISTNAME}${EXTRACT_SUFX}:apache,cpan MAINTAINER= scheidell@secnap.net COMMENT= A highly efficient mail filter for identifying spam RUN_DEPENDS= p5-NetAddr-IP>=4.00.7:${PORTSDIR}/net-mgmt/p5-NetAddr-IP \ p5-Net-DNS>=0.63:${PORTSDIR}/dns/p5-Net-DNS \ p5-HTML-Parser>=3.46:${PORTSDIR}/www/p5-HTML-Parser \ p5-libwww>=0:${PORTSDIR}/www/p5-libwww \ ${SITE_PERL}/${PERL_ARCH}/Encode/Detect.pm:${PORTSDIR}/converters/p5-Encode-Detect \ ${SITE_PERL}/Mail/Internet.pm:${PORTSDIR}/mail/p5-Mail-Tools BUILD_DEPENDS= p5-NetAddr-IP>=4.00.7:${PORTSDIR}/net-mgmt/p5-NetAddr-IP \ p5-Net-DNS>=0.63:${PORTSDIR}/dns/p5-Net-DNS \ p5-HTML-Parser>=3.46:${PORTSDIR}/www/p5-HTML-Parser \ p5-libwww>=0:${PORTSDIR}/www/p5-libwww \ ${SITE_PERL}/${PERL_ARCH}/Encode/Detect.pm:${PORTSDIR}/converters/p5-Encode-Detect \ ${SITE_PERL}/Mail/Internet.pm:${PORTSDIR}/mail/p5-Mail-Tools CONFLICTS= ja-p5-Mail-SpamAssassin-[0-9]* PERL_CONFIGURE= yes USE_PERL5_RUN= 5.8.8+ USE_LDCONFIG= yes DBDIR?= /var/db CONTACT_ADDRESS?= The administrator of that system USERS?= spamd GROUPS?= spamd CONFIGURE_ARGS= SYSCONFDIR="${PREFIX}/etc" \ CONTACT_ADDRESS="${CONTACT_ADDRESS}" \ LOCALSTATEDIR="${DBDIR}/spamassassin" OPTIONS= AS_ROOT "Run spamd as root (recommended)" on \ SPAMC "Build spamd/spamc (not for amavisd)" on \ SACOMPILE "sa-compile" off \ DKIM "DKIM/DomainKeys Identified Mail" on \ SSL "Build with SSL support for spamd/spamc" on \ GNUPG "Install GnuPG (for sa-update)" on \ MYSQL "Add MySQL support" off \ PGSQL "Add PostreSQL support" off \ RAZOR "Add Vipul's Razor support" on \ SPF_QUERY "Add SPF query support" off \ RELAY_COUNTRY "Relay country support" off \ DCC "Add DCC support (see LICENSE)" off .if !defined(WITHOUT_SSL) USE_OPENSSL= yes .endif .include .if ${PERL_LEVEL} < 500903 RUN_DEPENDS+= p5-IO-Compress>=2.017:${PORTSDIR}/archivers/p5-IO-Compress .endif .if ${PERL_LEVEL} < 501000 RUN_DEPENDS+= p5-Archive-Tar>=1.23:${PORTSDIR}/archivers/p5-Archive-Tar \ p5-IO-Zlib>=1.04:${PORTSDIR}/archivers/p5-IO-Zlib \ p5-Test-Harness>=3.16:${PORTSDIR}/devel/p5-Test-Harness .endif .if defined (WITH_SPAMC) CONFIGURE_ARGS+= BUILD_SPAMC=yes .else CONFIGURE_ARGS+= BUILD_SPAMC=no WITH_AS_ROOT= WITHOUT_SSL=1 .endif .if defined(WITH_SPF_QUERY) RUN_DEPENDS+= ${SITE_PERL}/Mail/SPF.pm:${PORTSDIR}/mail/p5-Mail-SPF .endif .if !defined(WITHOUT_IPV6) RUN_DEPENDS+= ${SITE_PERL}/IO/Socket/INET6.pm:${PORTSDIR}/net/p5-IO-Socket-INET6 .endif .if !defined(WITHOUT_SSL) .include "${PORTSDIR}/Mk/bsd.openssl.mk" RUN_DEPENDS+= ${SITE_PERL}/IO/Socket/SSL.pm:${PORTSDIR}/security/p5-IO-Socket-SSL CFLAGS+= -I${OPENSSLINC} LDFLAGS+= -L${OPENSSLLIB} CONFIGURE_ARGS+= ENABLE_SSL=yes PLIST_SUB+= SSL="" .else CONFIGURE_ARGS+= ENABLE_SSL=no PLIST_SUB+= SSL="@comment " .endif .if !defined(WITHOUT_GNUPG) RUN_DEPENDS+= gnupg>=1.4.7:${PORTSDIR}/security/gnupg .endif .if defined(WITH_MYSQL) RUN_DEPENDS+= ${SITE_PERL}/${PERL_ARCH}/DBD/mysql.pm:${PORTSDIR}/databases/p5-DBD-mysql .endif .if defined(WITH_PGSQL) RUN_DEPENDS+= ${SITE_PERL}/${PERL_ARCH}/DBD/Pg.pm:${PORTSDIR}/databases/p5-DBD-Pg .endif .if defined(WITH_RAZOR) RUN_DEPENDS+= razor-agents>=2.84:${PORTSDIR}/mail/razor-agents .else .if ${PERL_LEVEL} < 501000 .if !defined(WITH_DKIM) RUN_DEPENDS+= p5-Digest-SHA1>=2.11:${PORTSDIR}/security/p5-Digest-SHA1 .endif .endif .endif .if defined(WITH_DKIM) RUN_DEPENDS+= ${SITE_PERL}/IO/Socket/SSL.pm:${PORTSDIR}/security/p5-IO-Socket-SSL . if ${PERL_LEVEL} < 501000 RUN_DEPENDS+= ${SITE_PERL}/${PERL_ARCH}/Digest/SHA.pm:${PORTSDIR}/security/p5-Digest-SHA . endif RUN_DEPENDS+= p5-Mail-DKIM>=0.37:${PORTSDIR}/mail/p5-Mail-DKIM . if ${PERL_LEVEL} < 501400 RUN_DEPENDS+= p5-Crypt-OpenSSL-RSA>=0.24:${PORTSDIR}/security/p5-Crypt-OpenSSL-RSA . else RUN_DEPENDS+= p5-Crypt-OpenSSL-RSA>=0.26_1:${PORTSDIR}/security/p5-Crypt-OpenSSL-RSA . endif .endif .if defined(WITH_SACOMPILE) RUN_DEPENDS+= re2c>=.12.0:${PORTSDIR}/devel/re2c .endif .if defined(WITH_RELAY_COUNTRY) RUN_DEPENDS+= ${SITE_PERL}/IP/Country/Fast.pm:${PORTSDIR}/net/p5-IP-Country .endif .if defined(WITH_DCC) RUN_DEPENDS+= dcc-dccd>=1.3.111:${PORTSDIR}/mail/dcc-dccd .endif MAN3= Mail::SpamAssassin.3 \ Mail::SpamAssassin::AICache.3 \ Mail::SpamAssassin::ArchiveIterator.3 \ Mail::SpamAssassin::AsyncLoop.3 \ Mail::SpamAssassin::AutoWhitelist.3 \ Mail::SpamAssassin::Bayes.3 \ Mail::SpamAssassin::BayesStore.3 \ Mail::SpamAssassin::BayesStore::BDB.3 \ Mail::SpamAssassin::BayesStore::MySQL.3 \ Mail::SpamAssassin::BayesStore::PgSQL.3 \ Mail::SpamAssassin::BayesStore::SQL.3 \ Mail::SpamAssassin::Client.3 \ Mail::SpamAssassin::Conf.3 \ Mail::SpamAssassin::Conf::LDAP.3 \ Mail::SpamAssassin::Conf::Parser.3 \ Mail::SpamAssassin::Conf::SQL.3 \ Mail::SpamAssassin::DnsResolver.3 \ Mail::SpamAssassin::Logger.3 \ Mail::SpamAssassin::Logger::File.3 \ Mail::SpamAssassin::Logger::Stderr.3 \ Mail::SpamAssassin::Logger::Syslog.3 \ Mail::SpamAssassin::Message.3 \ Mail::SpamAssassin::Message::Metadata.3 \ Mail::SpamAssassin::Message::Node.3 \ Mail::SpamAssassin::PerMsgLearner.3 \ Mail::SpamAssassin::PerMsgStatus.3 \ Mail::SpamAssassin::PersistentAddrList.3 \ Mail::SpamAssassin::Plugin.3 \ Mail::SpamAssassin::Plugin::ASN.3 \ Mail::SpamAssassin::Plugin::AWL.3 \ Mail::SpamAssassin::Plugin::AccessDB.3 \ Mail::SpamAssassin::Plugin::AntiVirus.3 \ Mail::SpamAssassin::Plugin::AutoLearnThreshold.3 \ Mail::SpamAssassin::Plugin::Bayes.3 \ Mail::SpamAssassin::Plugin::BodyRuleBaseExtractor.3 \ Mail::SpamAssassin::Plugin::Check.3 \ Mail::SpamAssassin::Plugin::DCC.3 \ Mail::SpamAssassin::Plugin::DKIM.3 \ Mail::SpamAssassin::Plugin::Hashcash.3 \ Mail::SpamAssassin::Plugin::MIMEHeader.3 \ Mail::SpamAssassin::Plugin::OneLineBodyRuleType.3 \ Mail::SpamAssassin::Plugin::PhishTag.3 \ Mail::SpamAssassin::Plugin::Pyzor.3 \ Mail::SpamAssassin::Plugin::Razor2.3 \ Mail::SpamAssassin::Plugin::RelayCountry.3 \ Mail::SpamAssassin::Plugin::ReplaceTags.3 \ Mail::SpamAssassin::Plugin::Reuse.3 \ Mail::SpamAssassin::Plugin::Rule2XSBody.3 \ Mail::SpamAssassin::Plugin::SPF.3 \ Mail::SpamAssassin::Plugin::Shortcircuit.3 \ Mail::SpamAssassin::Plugin::SpamCop.3 \ Mail::SpamAssassin::Plugin::Test.3 \ Mail::SpamAssassin::Plugin::TextCat.3 \ Mail::SpamAssassin::Plugin::URIDNSBL.3 \ Mail::SpamAssassin::Plugin::URIDetail.3 \ Mail::SpamAssassin::Plugin::VBounce.3 \ Mail::SpamAssassin::Plugin::WhiteListSubject.3 \ Mail::SpamAssassin::PluginHandler.3 \ Mail::SpamAssassin::SQLBasedAddrList.3 \ Mail::SpamAssassin::SubProcBackChannel.3 \ Mail::SpamAssassin::Timeout.3 \ Mail::SpamAssassin::Util.3 \ Mail::SpamAssassin::Util::DependencyInfo.3 \ Mail::SpamAssassin::Util::Progress.3 \ Mail::SpamAssassin::Util::RegistrarBoundaries.3 \ spamassassin-run.3 MAN1= spamd.1 spamassassin.1 spamc.1 sa-learn.1 sa-update.1 \ spamassassin-run.1 sa-compile.1 sa-awl.1 DOCSDIR= ${PREFIX}/share/doc/${PKGNAMEPREFIX}${PORTNAME} DATADIR= ${PREFIX}/share/spamassassin DOCS= CREDITS Changes INSTALL LICENSE NOTICE PACKAGING README TRADEMARK UPGRADE USAGE procmailrc.example DOCSSQL= README README.awl README.bayes awl_mysql.sql awl_pg.sql bayes_mysql.sql bayes_pg.sql userpref_mysql.sql userpref_pg.sql DOCSLDAP= README README.testing sa_test.ldif PORTDOCS= ${DOCS} sql ldap USE_RC_SUBR= sa-spamd.sh .if defined(WITH_MYSQL) || defined(WITH_PGSQL) SUB_LIST+= SQL_FLAG="-Q" .else SUB_LIST+= SQL_FLAG="" .endif .if !defined(WITH_AS_ROOT) SUB_LIST+= RUN_AS_USER="-u ${USERS} -H /var/spool/spamd" .else SUB_LIST+= RUN_AS_USER="" .endif post-patch: @${FIND} ${WRKSRC} -name \*.orig -delete @${REINPLACE_CMD} -e 's#B_CONFDIR)/local.cf#B_CONFDIR)/local.cf.sample#g' \ -e 's#B_CONFDIR)/init.pre#B_CONFDIR)/init.pre.sample#g' \ -e 's#B_CONFDIR)/v310.pre#B_CONFDIR)/v310.pre.sample#g' \ -e 's#B_CONFDIR)/v312.pre#B_CONFDIR)/v312.pre.sample#g' \ -e 's#B_CONFDIR)/v320.pre#B_CONFDIR)/v320.pre.sample#g' \ -e 's#B_CONFDIR)/v330.pre#B_CONFDIR)/v330.pre.sample#g' \ -e 's/require DBI/0/' \ ${WRKSRC}/Makefile.PL @${REINPLACE_CMD} -e '/^CC =/d; \ s|@SSLCFLAGS@|& $${CFLAGS}|g' ${WRKSRC}/spamc/Makefile.in .if defined(WITH_RAZOR) ${REINPLACE_CMD} -e '/Razor2/s/^#loadplugin/loadplugin/' ${WRKSRC}/rules/v312.pre .endif .if defined(WITH_RELAY_COUNTRY) ${REINPLACE_CMD} -e '/RelayCountry/s/^# ?loadplugin/loadplugin/' ${WRKSRC}/rules/init.pre .endif .if !defined(WITH_DKIM) ${REINPLACE_CMD} -e '/DKIM/s/^loadplugin/#loadplugin/' ${WRKSRC}/rules/v312.pre .endif .if !defined(WITH_SPF_QUERY) ${REINPLACE_CMD} -e '/SPF/s/^loadplugin/#loadplugin/' ${WRKSRC}/rules/init.pre .endif .if defined(WITH_DCC) ${REINPLACE_CMD} -e '/DCC/s/^#loadplugin/loadplugin/' ${WRKSRC}/rules/v310.pre .endif .if defined(WITH_SACOMPILE) ${REINPLACE_CMD} -e '/Rule2XSBody/s/^# loadplugin/loadplugin/' ${WRKSRC}/rules/v320.pre .endif pre-install: @${MKDIR} ${DATADIR} post-build: @(cd ${BUILD_WRKSRC}; ${SETENV} ${MAKE_ENV} ${MAKE} ${MAKE_FLAGS} ${MAKEFILE} ${MAKE_ARGS} spamc/libspamc.so) .if !defined(WITHOUT_SSL) @(cd ${BUILD_WRKSRC}; ${SETENV} ${MAKE_ENV} ${MAKE} ${MAKE_FLAGS} ${MAKEFILE} ${MAKE_ARGS} spamc/libsslspamc.so) .endif pre-su-install: @PREFIX=${PREFIX} BATCH=${BATCH} SU_CMD="${SU_CMD}" USER=${USERS} GROUP=${GROUPS} INSTALL="${INSTALL}" ${SH} ${PKGINSTALL} ${PKGNAME} PRE-INSTALL @${INSTALL_PROGRAM} ${WRKSRC}/spamc/libspamc.so ${PREFIX}/lib/libspamc.so.0 @${LN} -sf libspamc.so.0 ${PREFIX}/lib/libspamc.so .if !defined(WITHOUT_SSL) @${INSTALL_PROGRAM} ${WRKSRC}/spamc/libsslspamc.so ${PREFIX}/lib/libsslspamc.so.0 @${LN} -sf libsslspamc.so.0 ${PREFIX}/lib/libsslspamc.so .endif @${INSTALL_DATA} ${WRKSRC}/spamc/libspamc.h ${PREFIX}/include post-install: .if defined (WITH_SPAMC) @${STRIP_CMD} ${PREFIX}/bin/spamc .endif @[ -f ${PREFIX}/etc/mail/spamassassin/init.pre ] || \ ${CP} ${PREFIX}/etc/mail/spamassassin/init.pre.sample \ ${PREFIX}/etc/mail/spamassassin/init.pre @[ -f ${PREFIX}/etc/mail/spamassassin/v310.pre ] || \ ${CP} ${PREFIX}/etc/mail/spamassassin/v310.pre.sample \ ${PREFIX}/etc/mail/spamassassin/v310.pre @[ -f ${PREFIX}/etc/mail/spamassassin/v312.pre ] || \ ${CP} ${PREFIX}/etc/mail/spamassassin/v312.pre.sample \ ${PREFIX}/etc/mail/spamassassin/v312.pre @[ -f ${PREFIX}/etc/mail/spamassassin/v320.pre ] || \ ${CP} ${PREFIX}/etc/mail/spamassassin/v320.pre.sample \ ${PREFIX}/etc/mail/spamassassin/v320.pre @PREFIX=${PREFIX} BATCH=${BATCH} SU_CMD="${SU_CMD}" USER=${USERS} GROUP=${GROUPS} INSTALL="${INSTALL}" ${SH} ${PKGINSTALL} ${PKGNAME} POST-INSTALL @[ -f ${PREFIX}/etc/mail/spamassassin/v330.pre ] || \ ${CP} ${PREFIX}/etc/mail/spamassassin/v330.pre.sample \ ${PREFIX}/etc/mail/spamassassin/v330.pre .if !defined(NOPORTDOCS) @${MKDIR} ${DOCSDIR} ${DOCSDIR}/sql ${DOCSDIR}/ldap @${INSTALL_DATA} ${DOCS:S|^|${WRKSRC}/|} ${DOCSDIR} @${INSTALL_DATA} ${DOCSSQL:S|^|${WRKSRC}/sql/|} ${DOCSDIR}/sql @${INSTALL_DATA} ${DOCSLDAP:S|^|${WRKSRC}/ldap/|} ${DOCSDIR}/ldap .endif @${SED} -e 's#PREFIX#${PREFIX}#' ${PKGMESSAGE} .include Property changes on: head/mail/p5-Mail-SpamAssassin/Makefile ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.143 \ No newline at end of property +1.144 \ No newline at end of property Index: head/mail/p5-Mail-SpamAssassin/files/patch-bug6698 =================================================================== --- head/mail/p5-Mail-SpamAssassin/files/patch-bug6698 (revision 288061) +++ head/mail/p5-Mail-SpamAssassin/files/patch-bug6698 (revision 288062) @@ -1,1471 +1,1471 @@ --- lib/Mail/SpamAssassin/Plugin/DCC.pm 2011-06-06 19:59:17.000000000 -0400 +++ lib/Mail/SpamAssassin/Plugin/DCC.pm 2011-11-26 07:22:36.000000000 -0500 @@ -15,6 +15,20 @@ # limitations under the License. # +# Changes since SpamAssassin 3.3.2: +# support for DCC learning. See dcc_learn_score. +# deal with orphan dccifd sockets +# use `cdcc -q` to not stall waiting to find a DCC server when deciding +# whether DCC checks are enabled +# use dccproc -Q or dccifd query if a pre-existing X-DCC header shows +# the message has already been reported +# dccproc now uses -w /var/dcc/whiteclnt so it acts more like dccifd +# warn about the use of ancient versions of dccproc and dccifd +# turn off dccifd greylisting +# query instead of reporting mail messages that contain X-DCC headers and +# and so has probably already been reported +# try harder to find dccproc and cdcc when not explicitly configured + =head1 NAME Mail::SpamAssassin::Plugin::DCC - perform DCC check of messages @@ -30,30 +44,31 @@ The DCC or Distributed Checksum Clearinghouse is a system of servers collecting and counting checksums of millions of mail messages. -TheSpamAssassin.pm counts can be used by SpamAssassin to detect and -reject or filter spam. - -Because simplistic checksums of spam can be easily defeated, the main -DCC checksums are fuzzy and ignore aspects of messages. The fuzzy -checksums are changed as spam evolves. +The counts can be used by SpamAssassin to detect and filter spam. -Note that DCC is disabled by default in C because it is not -open source. See the DCC license for more details. +See http://www.dcc-servers.net/dcc/ for more information about DCC. -See http://www.rhyolite.com/anti-spam/dcc/ for more information about -DCC. +Note that DCC is disabled by default in C because its use requires +software that is not distributed with SpamAssassin and that has license +restrictions for certain commercial uses. +See the DCC license at http://www.dcc-servers.net/dcc/LICENSE for details. + +Enable it by uncommenting the "loadplugin Mail::SpamAssassin::Plugin::DCC" +confdir/v310.pre or by adding this line to your local.pre. It might also +be necessary to install a DCC package, port, rpm, or equivalent from your +operating system distributor or a tarball from the primary DCC source +at http://www.dcc-servers.net/dcc/#download +See also http://www.dcc-servers.net/dcc/INSTALL.html =head1 TAGS The following tags are added to the set, available for use in reports, header fields, other plugins, etc.: - _DCCB_ DCC server ID in a response - _DCCR_ response from DCC - header field body in X-DCC-*-Metrics - _DCCREP_ response from DCC - DCC reputation in percents (0..100) - -Tag _DCCREP_ provides a nonempty value only with commercial DCC systems. -This is the percentage of spam vs. ham sent from the first untrusted relay. + _DCCB_ DCC server ID in X-DCC-*-Metrics header field name + _DCCR_ X-DCC-*-Metrics header field body + _DCCREP_ DCC Reputation or percent bulk mail (0..100) from + commercial DCC software =cut @@ -75,8 +90,6 @@ use vars qw(@ISA); @ISA = qw(Mail::SpamAssassin::Plugin); -use vars qw($have_inet6); - sub new { my $class = shift; my $mailsaobject = shift; @@ -87,7 +100,7 @@ # are network tests enabled? if ($mailsaobject->{local_tests_only}) { - $self->{dcc_disabled} = 1; + $self->{use_dcc} = 0; dbg("dcc: local tests only, disabling DCC"); } else { @@ -128,20 +141,23 @@ =item dcc_fuz2_max NUMBER -This option sets how often a message's body/fuz1/fuz2 checksum must have been -reported to the DCC server before SpamAssassin will consider the DCC check as -matched. - -As nearly all DCC clients are auto-reporting these checksums, you should set -this to a relatively high value, e.g. C<999999> (this is DCC's MANY count). +Sets how often a message's body/fuz1/fuz2 checksum must have been reported +to the DCC server before SpamAssassin will consider the DCC check hit. +C<999999> is DCC's MANY count. The default is C<999999> for all these options. =item dcc_rep_percent NUMBER -Only commercial DCC systems provide DCC reputation information. This is the -percentage of spam vs. ham sent from the first untrusted relay. It will hit -on new spam from spam sources. Default is C<90>. +Only the commercial DCC software provides DCC Reputations. A DCC Reputation +is the percentage of bulk mail received from the last untrusted relay in the +path taken by a mail message as measured by all commercial DCC installations. +See http://www.rhyolite.com/dcc/reputations.html +You C whitelist your trusted relays or MX servers with MX or +MXDCC lines in /var/dcc/whiteclnt as described in the main DCC man page +to avoid seeing your own MX servers as sources of bulk mail. +See http://www.dcc-servers.net/dcc/dcc-tree/dcc.html#White-and-Blacklists +The default is C<90>. =cut @@ -189,13 +205,9 @@ =item dcc_home STRING This option tells SpamAssassin where to find the dcc homedir. -If not given, it will try to get dcc to specify one, and if that fails it -will try dcc's own default homedir of '/var/dcc'. -If C is not specified, it will default to looking in -C for dcc client instead of relying on SpamAssassin to find it -in the current PATH. If it isn't found there, it will look in the current -PATH. If a C socket is found in C or specified explicitly, -it will use that interface instead of C. +If not specified, try to use the locally configured directory +from the C command. +Try /var/dcc if that command fails. =cut @@ -205,7 +217,7 @@ type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, code => sub { my ($self, $key, $value, $line) = @_; - if (!defined $value || !length $value) { + if (!defined $value || $value eq '') { return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; } $value = untaint_file_path($value); @@ -223,14 +235,16 @@ =item dcc_dccifd_path STRING -This option tells SpamAssassin where to find the dccifd socket. If -C is not specified, it will default to looking for a socket -named C in a directory C. The C can be -a Unix socket name (absolute path), or an INET socket specification in a form -C<[host]:port> or C, where a host can be an IPv4 or IPv6 address -or a host name, and port is a TCP port number. In case of an IPv6 address the -brackets are required syntax. If a C socket is found, the plugin will -use it instead of C. +This option tells SpamAssassin where to find the dccifd socket instead +of a local Unix socket named C in the C directory. +If a socket is specified or found, use it instead of C. + +If specifed, C is the absolute path of local Unix socket +or an INET socket specified as C<[Host]:Port> or C. +Host can be an IPv4 or IPv6 address or a host name +Port is a TCP port number. The brackets are required for an IPv6 address. + +The default is C. =cut @@ -240,45 +254,60 @@ type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, code => sub { my ($self, $key, $value, $line) = @_; - $value = '' if !defined $value; - $self->{dcc_dccifd_path_raw} = $value; # for logging purposes - undef $self->{dcc_dccifd_host}; - undef $self->{dcc_dccifd_port}; - undef $self->{dcc_dccifd_socket}; - local($1,$2,$3); - if ($value eq '') { + + if (!defined $value || $value eq '') { return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; - } elsif ($value =~ m{^ (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) \z}sx) { - # "[host]:port" or "host:port", where a host can be an IPv4 or IPv6 - # address or a host name, and port is a TCP port number or service name - my $host = defined $1 ? $1 : $2; - my $port = $3; - $self->{dcc_dccifd_host} = untaint_var($host); - $self->{dcc_dccifd_port} = untaint_var($port); - dbg("config: dcc_dccifd_path set to [%s]:%s", $host,$port); - } else { # assume a unix socket + } + + local($1,$2,$3); + if ($value =~ m{^ (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*) \z}sx) { + my $host = untaint_var(defined $1 ? $1 : $2); + my $port = untaint_var($3); + if (!$host) { + info("config: missing or bad host name in dcc_dccifd_path '$value'"); + return $Mail::SpamAssassin::Conf::INVALID_VALUE; + } + if (!$port || $port !~ /^\d+\z/ || $port < 1 || $port > 65535) { + info("config: bad TCP port number in dcc_dccifd_path '$value'"); + return $Mail::SpamAssassin::Conf::INVALID_VALUE; + } + + $self->{dcc_dccifd_host} = $host; + $self->{dcc_dccifd_port} = $port; + if ($host !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/) { + # remember to try IPv6 if we can with a host name or non-IPv4 address + $self->{dcc_dccifd_IPv6} = eval { require IO::Socket::INET6 }; + } + dbg("config: dcc_dccifd_path set to [%s]:%s", $host, $port); + + } else { + # assume a unix socket if ($value !~ m{^/}) { - info("config: dcc_dccifd_path should be an absolute socket path"); + info("config: dcc_dccifd_path '$value' is not an absolute path"); # return $Mail::SpamAssassin::Conf::INVALID_VALUE; # abort or accept? } $value = untaint_file_path($value); - # test disabled, dccifd may not yet be running at spamd startup time - # if (!-S $value) { - # info("config: dcc_dccifd_path '$value' isn't a local socket"); - # return $Mail::SpamAssassin::Conf::INVALID_VALUE; - # } + $self->{dcc_dccifd_socket} = $value; dbg("config: dcc_dccifd_path set to local socket %s", $value); + dbg("dcc: dcc_dccifd_path set to local socket %s", $value); } + + $self->{dcc_dccifd_path_raw} = $value; } }); =item dcc_path STRING -This option tells SpamAssassin specifically where to find the C -client instead of relying on SpamAssassin to find it in the current PATH. -Note that if I is enabled in the Perl interpreter, you should -use this, as the current PATH will have been cleared. +Where to find the C client program instead of relying on SpamAssassin +to find it in the current PATH or C. This must often be set, +because the current PATH is cleared by I in the Perl interpreter, + +If a C socket is found in C or specified explicitly +with C, use the C interface instead of C. + +The default is C. + =cut @@ -289,12 +318,12 @@ type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, code => sub { my ($self, $key, $value, $line) = @_; - if (!defined $value || !length $value) { + if (!defined $value || $value eq '') { return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE; } $value = untaint_file_path($value); if (!-x $value) { - info("config: dcc_path '$value' isn't an executable"); + info("config: dcc_path '$value' is not executable"); return $Mail::SpamAssassin::Conf::INVALID_VALUE; } @@ -304,7 +333,7 @@ =item dcc_options options -Specify additional options to the dccproc(8) command. Please note that only +Specify additional options to the dccproc(8) command. Only characters in the range [0-9A-Za-z ,._/-] are allowed for security reasons. The default is C. @@ -319,6 +348,7 @@ code => sub { my ($self, $key, $value, $line) = @_; if ($value !~ m{^([0-9A-Za-z ,._/-]+)$}) { + info("config: dcc_options '$value' contains impermissible characters"); return $Mail::SpamAssassin::Conf::INVALID_VALUE; } $self->{dcc_options} = $1; @@ -327,8 +357,9 @@ =item dccifd_options options -Specify additional options to send to the dccifd(8) daemon. Please note that only -characters in the range [0-9A-Za-z ,._/-] are allowed for security reasons. +Specify additional options to send to the dccifd daemon with +the ASCII protocol described on the dccifd(8) man page. +Only characters in the range [0-9A-Za-z ,._/-] are allowed for security reasons. The default is C. @@ -342,265 +373,306 @@ code => sub { my ($self, $key, $value, $line) = @_; if ($value !~ m{^([0-9A-Za-z ,._/-]+)$}) { + info("config: dccifd_options '$value' contains impermissible characters"); return $Mail::SpamAssassin::Conf::INVALID_VALUE; } $self->{dccifd_options} = $1; } }); +=item dcc_learn_score n (default: undef) + +Report messages with total scores this much larger than the +SpamAssassin spam threshold to DCC as spam. + +=cut + + push (@cmds, { + setting => 'dcc_learn_score', + is_admin => 1, + default => undef, + type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, + }); + $conf->{parser}->register_commands(\@cmds); } + + + +sub ck_dir { + my ($self, $dir, $tgt, $src) = @_; + + $dir = untaint_file_path($dir); + if (!stat($dir)) { + my $dir_errno = 0+$!; + if ($dir_errno == ENOENT) { + dbg("dcc: $tgt $dir from $src does not exist"); + } else { + dbg("dcc: $tgt $dir from $src is not accessible: $!"); + } + return; + } + if (!-d _) { + dbg("dcc: $tgt $dir from $src is not a directory"); + return; + } + + $self->{main}->{conf}->{$tgt} = $dir; + dbg("dcc: use '$tgt $dir' from $src"); +} + sub find_dcc_home { my ($self) = @_; + my $dcc_libexec; + + # just once + return if defined $self->{dcc_version}; + $self->{dcc_version} = '?'; my $conf = $self->{main}->{conf}; - return if !$conf->{use_dcc}; - my $dcchome = $conf->{dcc_home} || ''; - # If we're not given the DCC homedir, try getting DCC to tell us it. - # If that fails, try the DCC default homedir of '/var/dcc'. - if ($dcchome eq '') { + # Get the DCC software version for talking to dccifd and formating the + # dccifd options and the built-in DCC homedir. Use -q to prevent delays. + my $cdcc_home; + my $cdcc = $self->dcc_pgm_path('cdcc'); + my $cmd = '-qV homedir libexecdir'; + if ($cdcc && open(CDCC, "$cdcc $cmd 2>&1 |")) { + my $cdcc_output = do { local $/ = undef; }; + close CDCC; - my $cdcc = Mail::SpamAssassin::Util::find_executable_in_env_path('cdcc'); + $cdcc_output =~ s/\n/ /g; # everything in 1 line for debugging + dbg("dcc: `%s %s` reports '%s'", $cdcc, $cmd, $cdcc_output); + $self->{dcc_version} = ($cdcc_output =~ /^(\d+\.\d+\.\d+)/) ? $1 : ''; + $cdcc_home = ($cdcc_output =~ /\s+homedir=(\S+)/) ? $1 : ''; + if ($cdcc_output =~ /\s+libexecdir=(\S+)/) { + $self->ck_dir($1, 'dcc_libexec', 'cdcc'); + } + } - my $cdcc_home = ''; - if ($cdcc && -x $cdcc && open(CDCC, "$cdcc homedir 2>&1|")) { - dbg("dcc: dcc_home not set, querying cdcc utility"); - $cdcc_home = || ''; - close CDCC; + # without a home, try the homedir from cdcc + if (!$conf->{dcc_home} && $cdcc_home) { + $self->ck_dir($cdcc_home, 'dcc_home', 'cdcc'); + } + # finally fall back to /var/dcc + if (!$conf->{dcc_home}) { + $self->ck_dir($conf->{dcc_home} = '/var/dcc', 'dcc_home', 'default') + } - chomp $cdcc_home; - $cdcc_home =~ s/\s+homedir=//; - dbg("dcc: cdcc reports homedir as '%s'", $cdcc_home); - } - - # try first with whatever the cdcc utility reported - my $cdcc_home_errno = 0; - if ($cdcc_home eq '') { - $cdcc_home_errno = ENOENT; - } elsif (!stat($cdcc_home)) { - $cdcc_home_errno = 0+$!; - } - if ($cdcc_home_errno == ENOENT) { - # no such file - } elsif ($cdcc_home_errno != 0) { - dbg("dcc: cdcc reported homedir $cdcc_home is not accessible: $!"); - } elsif (!-d _) { - dbg("dcc: cdcc reported homedir $cdcc_home is not a directory"); - } else { # ok - dbg("dcc: cdcc reported homedir $cdcc_home exists, using it"); - $dcchome = untaint_var($cdcc_home); - } - - # try falling back to /var/dcc - if ($dcchome eq '') { - my $var_dcc_errno = stat('/var/dcc') ? 0 : 0+$!; - if ($var_dcc_errno == ENOENT) { - # no such file - } elsif ($var_dcc_errno != 0) { - dbg("dcc: dcc_home not set and dcc default homedir /var/dcc ". - "is not accessible: $!"); - } elsif (!-d _) { - dbg("dcc: dcc_home not set and dcc default homedir /var/dcc ". - "is not a directory"); - } else { # ok - dbg("dcc: dcc_home not set but dcc default homedir /var/dcc exists, ". - "using it"); - $dcchome = '/var/dcc'; + # fall back to $conf->{dcc_home}/libexec or /var/dcc/libexec for dccsight + if (!$conf->{dcc_libexec}) { + $self->ck_dir($conf->{dcc_home} . '/libexec', 'dcc_libexec', 'dcc_home'); } + if (!$conf->{dcc_libexec}) { + $self->ck_dir('/var/dcc/libexec', 'dcc_libexec', 'dcc_home'); } - if ($dcchome eq '') { - dbg("dcc: unable to get homedir from cdcc ". - "and the dcc default homedir was not found"); - } - - # Remember found homedir path - dbg("dcc: using '%s' as DCC homedir", $dcchome); - $conf->{dcc_home} = $dcchome; + # format options for dccifd + my $opts = ($conf->{dccifd_options} || '') . "\n"; + if ($self->{dcc_version} =~ /\d+\.(\d+)\.(\d+)$/ && + ($1 < 3 || ($1 == 3 && $2 < 123))) { + if ($1 < 3 || ($1 == 3 && $2 < 50)) { + info("dcc: DCC version $self->{dcc_version} is years old, ". + "obsolete, and likely to cause problems. ". + "See http://www.dcc-servers.net/dcc/old-versions.html"); + } + $self->{dccifd_lookup_options} = "header " . $opts; + $self->{dccifd_report_options} = "header spam " . $opts; + } else { + # dccifd after version 1.2.123 understands "cksums" and "no-grey" + $self->{dccifd_lookup_options} = "cksums grey-off " . $opts; + $self->{dccifd_report_options} = "header spam grey-off " . $opts; } } -sub is_dccifd_available { - my ($self) = @_; - +sub dcc_pgm_path { + my ($self, $pgm) = @_; + my $pgmpath; my $conf = $self->{main}->{conf}; - $self->{dccifd_available} = 0; - if (!$conf->{use_dcc}) { - dbg("dcc: dccifd is not available: use_dcc is false"); - } elsif (defined $conf->{dcc_dccifd_host}) { - dbg("dcc: dccifd inet socket chosen: [%s]:%s", - $conf->{dcc_dccifd_host}, $conf->{dcc_dccifd_port}); - $self->{dccifd_available} = 1; - } else { - my $sockpath = $conf->{dcc_dccifd_socket}; - my $dcchome = $conf->{dcc_home}; - if (defined $sockpath) { - dbg("dcc: dccifd local socket chosen: %s", $sockpath); - } elsif (defined $conf->{dcc_dccifd_path_raw}) { - # avoid falling back to defaults if explicitly provided but wrong - } elsif (defined $dcchome && $dcchome ne '' && -S "$dcchome/dccifd") { - $sockpath = "$dcchome/dccifd"; - $conf->{dcc_dccifd_socket} = $sockpath; - dbg("dcc: dccifd default local socket chosen: %s", $sockpath); + $pgmpath = $conf->{dcc_path}; + if (defined $pgmpath && $pgmpath ne '') { + # accept explicit setting for dccproc + return $pgmpath if $pgm eq 'dccproc'; + # try adapting it for cdcc and everything else + if ($pgmpath =~ s{[^/]+\z}{$pgm}s) { + $pgmpath = untaint_file_path($pgmpath); + if (-x $pgmpath) { + dbg("dcc: dcc_pgm_path, found %s in dcc_path: %s", $pgm,$pgmpath); + return $pgmpath; } - if (defined $sockpath && -S $sockpath && -w _ && -r _) { - $self->{dccifd_available} = 1; - } elsif (!defined $conf->{dcc_dccifd_path_raw}) { - dbg("dcc: dccifd is not available: no r/w dccifd socket found"); - } else { - dbg("dcc: dccifd is not available: no r/w dccifd socket found: %s", - $conf->{dcc_dccifd_path_raw}); } } - return $self->{dccifd_available}; + $pgmpath = Mail::SpamAssassin::Util::find_executable_in_env_path($pgm); + if (defined $pgmpath) { + dbg("dcc: dcc_pgm_path, found %s in env.path: %s", $pgm,$pgmpath); + return $pgmpath; + } + + # try dcc_home/bin, dcc_libexec, and some desperate last attempts + foreach my $dir ($conf->{dcc_home}.'/bin', $conf->{dcc_libexec}, + '/usr/local/bin', '/usr/local/dcc', '/var/dcc') { + $pgmpath = $dir . '/' . $pgm; + if (-x $pgmpath) { + dbg("dcc: dcc_pgm_path, found %s in %s: %s", $pgm,$dir,$pgmpath); + return $pgmpath; + } + } + + return; } -sub is_dccproc_available { +sub is_dccifd_available { my ($self) = @_; my $conf = $self->{main}->{conf}; - $self->{dccproc_available} = 0; + # dccifd remains available until it breaks + return $self->{dccifd_available} if $self->{dccifd_available}; - if (!$conf->{use_dcc}) { - dbg("dcc: dccproc is not available: use_dcc is false"); - return 0; + # deal with configured INET socket + if (defined $conf->{dcc_dccifd_host}) { + dbg("dcc: dccifd is available via INET socket [%s]:%s", + $conf->{dcc_dccifd_host}, $conf->{dcc_dccifd_port}); + return ($self->{dccifd_available} = 1); } - my $dcchome = $conf->{dcc_home} || ''; - my $dccproc = $conf->{dcc_path} || ''; - if ($dccproc eq '' && ($dcchome ne '' && -x "$dcchome/bin/dccproc")) { - $dccproc = "$dcchome/bin/dccproc"; + # the first time here, compute a default local socket based on DCC home + # from self->find_dcc_home() called elsewhere + my $sockpath = $conf->{dcc_dccifd_socket}; + if (!$sockpath) { + if ($conf->{dcc_dccifd_path_raw}) { + $sockpath = $conf->{dcc_dccifd_path_raw}; + } else { + $sockpath = "$conf->{dcc_home}/dccifd"; } - if ($dccproc eq '') { - $dccproc = Mail::SpamAssassin::Util::find_executable_in_env_path('dccproc'); + $conf->{dcc_dccifd_socket} = $sockpath; } - unless (defined $dccproc && $dccproc ne '' && -x $dccproc) { - dbg("dcc: dccproc is not available: no dccproc executable found"); - return 0; - } + # check the socket every time because it can appear and disappear + return ($self->{dccifd_available} = 1) if (-S $sockpath && -w _ && -r _); - # remember any found dccproc + dbg("dcc: dccifd is not available; no r/w socket at %s", $sockpath); + return ($self->{dccifd_available} = 0); +} + +sub is_dccproc_available { + my ($self) = @_; + my $conf = $self->{main}->{conf}; + + # dccproc remains (un)available so check only once + return $self->{dccproc_available} if defined $self->{dccproc_available}; + + my $dccproc = $conf->{dcc_path}; + if (!defined $dccproc || $dccproc eq '') { + $dccproc = $self->dcc_pgm_path('dccproc'); $conf->{dcc_path} = $dccproc; + if (!$dccproc || ! -x $dccproc) { + dbg("dcc: dccproc is not available: no dccproc executable found"); + return ($self->{dccproc_available} = 0); + } + } - dbg("dcc: dccproc is available: %s", $conf->{dcc_path}); - $self->{dccproc_available} = 1; - return 1; + dbg("dcc: %s is available", $conf->{dcc_path}); + return ($self->{dccproc_available} = 1); } sub dccifd_connect { - my($self) = @_; + my($self, $tag) = @_; my $conf = $self->{main}->{conf}; my $sockpath = $conf->{dcc_dccifd_socket}; - my $host = $conf->{dcc_dccifd_host}; - my $port = $conf->{dcc_dccifd_port}; my $sock; + if (defined $sockpath) { - dbg("dcc: connecting to a local socket %s", $sockpath); - $sock = IO::Socket::UNIX->new( - Type => SOCK_STREAM, Peer => $sockpath); - $sock or die "dcc: failed to connect to a socket $sockpath: $!\n"; - } elsif (defined $host) { - my $specified_path = $conf->{dcc_dccifd_path_raw}; - if ($host eq '') { - die "dcc: empty host specification: $specified_path\n"; - } - if (!defined $port || $port !~ /^\d+\z/ || $port < 1 || $port > 65535) { - die "dcc: bad TCP port number: $specified_path\n"; - } - my $is_inet4 = $host =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/; - if ($is_inet4) { # inet4 socket (IPv4 address) - dbg("dcc: connecting to inet4 socket [%s]:%s", $host,$port); - $sock = IO::Socket::INET->new( - Proto => 'tcp', PeerAddr => $host, PeerPort => $port); - } else { - if (!defined $have_inet6) { - $have_inet6 = eval { require IO::Socket::INET6 }; - $have_inet6 = 0 if !defined $have_inet6; + $sock = IO::Socket::UNIX->new(Type => SOCK_STREAM, Peer => $sockpath); + if ($sock) { + dbg("$tag connected to local socket %s", $sockpath); + return $sock; } - if (!$have_inet6) { # fallback to an inet4 socket (IPv4) - dbg("dcc: connecting(2) to inet4 socket [%s]:%s", $host,$port); - $sock = IO::Socket::INET->new( - Proto => 'tcp', PeerAddr => $host, PeerPort => $port); - } else { # inet6 socket (IPv6) or a host name - dbg("dcc: connecting to inet6 socket [%s]:%s", $host,$port); + $self->{dccifd_available} = 0; + info("$tag failed to connect to local socket $sockpath"); + return $sock + } + + # must be TCP/IP + my $host = $conf->{dcc_dccifd_host}; + my $port = $conf->{dcc_dccifd_port}; + + if ($conf->{dcc_dccifd_IPv6}) { + # try IPv6 if we can with a host name or non-IPv4 address + dbg("$tag connecting to inet6 socket [%s]:%s", $host,$port); $sock = IO::Socket::INET6->new( Proto => 'tcp', PeerAddr => $host, PeerPort => $port); + # fall back to IPv4 if that failed } + if (!$sock) { + dbg("$tag connecting to inet4 socket [%s]:%s", $host, $port); + $sock = IO::Socket::INET->new( + Proto => 'tcp', PeerAddr => $host, PeerPort => $port); } - $sock or die "dcc: failed to connect to [$host]:$port : $!\n"; - } else { - die "dcc: dccifd socket not provided: $conf->{dcc_dccifd_path_raw}\n"; - } + + info("failed to connect to [$host]:$port : $!") if !$sock; return $sock; } +# check for dccifd every time in case enough uses of dccproc starts dccifd sub get_dcc_interface { my ($self) = @_; + my $conf = $self->{main}->{conf}; - if ($self->is_dccifd_available()) { - $self->{dcc_interface} = "dccifd"; - $self->{dcc_disabled} = 0; - } - elsif ($self->is_dccproc_available()) { - $self->{dcc_interface} = "dccproc"; - $self->{dcc_disabled} = 0; + if (!$conf->{use_dcc}) { + $self->{dcc_disabled} = 1; + return; } - else { - dbg("dcc: dccifd and dccproc are not available, disabling DCC"); - $self->{dcc_interface} = "none"; + + $self->find_dcc_home(); + if (!$self->is_dccifd_available() && !$self->is_dccproc_available()) { + dbg("dcc: dccifd and dccproc are not available"); $self->{dcc_disabled} = 1; } + + $self->{dcc_disabled} = 0; } sub dcc_query { - my ($self, $permsgstatus, $full) = @_; + my ($self, $permsgstatus, $fulltext) = @_; $permsgstatus->{dcc_checked} = 1; + if (!$self->{main}->{conf}->{use_dcc}) { + dbg("dcc: DCC is not available: use_dcc is 0"); + return; + } + # initialize valid tags $permsgstatus->{tag_data}->{DCCB} = ""; $permsgstatus->{tag_data}->{DCCR} = ""; $permsgstatus->{tag_data}->{DCCREP} = ""; - # short-circuit if there's already a X-DCC header with value of - # "bulk" from an upstream DCC check - if ($permsgstatus->get('ALL') =~ - /^(X-DCC-([^:]{1,80})?-?Metrics:.*bulk.*)$/m) { - $permsgstatus->{dcc_response} = $1; + if ($$fulltext eq '') { + dbg("dcc: empty message; skipping dcc check"); return; } - my $timer = $self->{main}->time_method("check_dcc"); + if ($permsgstatus->get('ALL') =~ /^(X-DCC-.*-Metrics:.*)$/m) { + $permsgstatus->{dcc_raw_x_dcc} = $1; + # short-circuit if there is already a X-DCC header with value of + # "bulk" from an upstream DCC check + # require "bulk" because then at least one body checksum will be "many" + # and so we know the X-DCC header is not forged by spammers + return if $permsgstatus->{dcc_raw_x_dcc} =~ / bulk /; + } - $self->find_dcc_home(); + my $timer = $self->{main}->time_method("check_dcc"); $self->get_dcc_interface(); - my $result; - if ($self->{dcc_disabled}) { - $result = 0; - } elsif ($$full eq '') { - dbg("dcc: empty message, skipping dcc check"); - $result = 0; - } elsif ($self->{dccifd_available}) { - my $client = $permsgstatus->{relays_external}->[0]->{ip}; - my $clientname = $permsgstatus->{relays_external}->[0]->{rdns}; - my $helo = $permsgstatus->{relays_external}->[0]->{helo} || ""; - if ($client) { - $client = $client . "\r" . $clientname if $clientname; - } else { - $client = "0.0.0.0"; - } - $self->dccifd_lookup($permsgstatus, $full, $client, $clientname, $helo); - } else { - my $client = $permsgstatus->{relays_external}->[0]->{ip}; - $self->dccproc_lookup($permsgstatus, $full, $client); - } + return if $self->{dcc_disabled}; + + my $envelope = $permsgstatus->{relays_external}->[0]; + ($permsgstatus->{dcc_raw_x_dcc}, + $permsgstatus->{dcc_cksums}) = $self->ask_dcc("dcc:", $permsgstatus, + $fulltext, $envelope); } sub check_dcc { @@ -609,28 +681,27 @@ $self->dcc_query($permsgstatus, $full) if !$permsgstatus->{dcc_checked}; - my $response = $permsgstatus->{dcc_response}; - return 0 if !defined $response || $response eq ''; + my $x_dcc = $permsgstatus->{dcc_raw_x_dcc}; + return 0 if !defined $x_dcc || $x_dcc eq ''; - local($1,$2); - if ($response =~ /^X-DCC-(.*)-Metrics: (.*)$/) { - $permsgstatus->{tag_data}->{DCCB} = $1; - $permsgstatus->{tag_data}->{DCCR} = $2; + if ($x_dcc =~ /^X-DCC-(.*)-Metrics: (.*)$/) { + $permsgstatus->set_tag('DCCB', $1); + $permsgstatus->set_tag('DCCR', $2); } - $response =~ s/many/999999/ig; - $response =~ s/ok\d?/0/ig; + $x_dcc =~ s/many/999999/ig; + $x_dcc =~ s/ok\d?/0/ig; my %count = (body => 0, fuz1 => 0, fuz2 => 0, rep => 0); - if ($response =~ /\bBody=(\d+)/) { + if ($x_dcc =~ /\bBody=(\d+)/) { $count{body} = $1+0; } - if ($response =~ /\bFuz1=(\d+)/) { + if ($x_dcc =~ /\bFuz1=(\d+)/) { $count{fuz1} = $1+0; } - if ($response =~ /\bFuz2=(\d+)/) { + if ($x_dcc =~ /\bFuz2=(\d+)/) { $count{fuz2} = $1+0; } - if ($response =~ /\brep=(\d+)/) { + if ($x_dcc =~ /\brep=(\d+)/) { $count{rep} = $1+0; } if ($count{body} >= $conf->{dcc_body_max} || @@ -651,185 +722,185 @@ } sub check_dcc_reputation_range { - my ($self, $permsgstatus, $full, $min, $max) = @_; - $self->dcc_query($permsgstatus, $full) if !$permsgstatus->{dcc_checked}; + my ($self, $permsgstatus, $fulltext, $min, $max) = @_; + + # this is called several times per message, so parse the X-DCC header once + my $dcc_rep = $permsgstatus->{dcc_rep}; + if (!defined $dcc_rep) { + $self->dcc_query($permsgstatus, $fulltext) if !$permsgstatus->{dcc_checked}; + my $x_dcc = $permsgstatus->{dcc_raw_x_dcc}; + if (defined $x_dcc && $x_dcc =~ /\brep=(\d+)/) { + $dcc_rep = $1+0; + $permsgstatus->set_tag('DCCREP', $dcc_rep); + } else { + $dcc_rep = -1; + } + $permsgstatus->{dcc_rep} = $dcc_rep; + } - my $response = $permsgstatus->{dcc_response}; - return 0 if !defined $response || $response eq ''; + # no X-DCC header or no reputation in the X-DCC header, perhaps for lack + # of data in the DCC Reputation server + return 0 if $dcc_rep < 0; + # cover the entire range of reputations if not told otherwise $min = 0 if !defined $min; - $max = 999 if !defined $max; + $max = 100 if !defined $max; - local $1; - my $dcc_rep; - $dcc_rep = $1+0 if defined $response && $response =~ /\brep=(\d+)/; - if (defined $dcc_rep) { - $dcc_rep = int($dcc_rep); # just in case, rule ranges are integer percents my $result = $dcc_rep >= $min && $dcc_rep <= $max ? 1 : 0; dbg("dcc: dcc_rep %s, min %s, max %s => result=%s", $dcc_rep, $min, $max, $result?'YES':'no'); - $permsgstatus->{tag_data}->{DCCREP} = $dcc_rep; - return $dcc_rep >= $min && $dcc_rep <= $max ? 1 : 0; + return $result; +} + +# get the X-DCC header line and save the checksums from dccifd or dccproc +sub parse_dcc_response { + my ($self, $resp) = @_; + my ($raw_x_dcc, $cksums); + + # The first line is the header we want. It uses SMTP folded whitespace + # if it is long. The folded whitespace is always a single \t. + chomp($raw_x_dcc = shift @$resp); + my $v; + while (($v = shift @$resp) && $v =~ s/^\t(.+)\s*\n/ $1/) { + $raw_x_dcc .= $v; + } + + # skip the "reported:" line between the X-DCC header and any checksums + # remove ':' to avoid a bug in versions 1.3.115 - 1.3.122 in dccsight + # with the length of "Message-ID:" + $cksums = ''; + while (($v = shift @$resp) && $v =~ s/^([^:]*):/$1/) { + $cksums .= $v; } - return 0; + + return ($raw_x_dcc, $cksums); } -sub dccifd_lookup { - my ($self, $permsgstatus, $fulltext, $client, $clientname, $helo) = @_; +sub ask_dcc { + my ($self, $tag, $permsgstatus, $fulltext, $envelope) = @_; my $conf = $self->{main}->{conf}; - my $response; - my $left; - my $right; - my $timeout = $conf->{dcc_timeout}; - my $opts = $conf->{dccifd_options}; - my @opts = !defined $opts ? () : split(' ',$opts); + my ($pgm, $err, $sock, $pid, @resp); + my ($client, $clientname, $helo, $opts); $permsgstatus->enter_helper_run_mode(); + my $timeout = $conf->{dcc_timeout}; my $timer = Mail::SpamAssassin::Timeout->new( { secs => $timeout, deadline => $permsgstatus->{master_deadline} }); - my $err = $timer->run_and_catch(sub { + $err = $timer->run_and_catch(sub { local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; - my $sock = $self->dccifd_connect(); - $sock or die "dcc: failed to connect to a dccifd socket"; - - # send the options and other parameters to the daemon - $sock->print("header " . join(" ",@opts) . "\n") - or die "dcc: failed write"; # options - $sock->print($client . "\n") or die "dcc: failed write"; # client - $sock->print($helo . "\n") or die "dcc: failed write"; # HELO value - $sock->print("\n") or die "dcc: failed write"; # sender - $sock->print("unknown\r\n") or die "dcc: failed write"; # recipients - $sock->print("\n") or die "dcc: failed write"; # recipients - - $sock->print($$fulltext) or die "dcc: failed write"; - - $sock->shutdown(1) or die "dcc: failed socket shutdown: $!"; - - $sock->getline() or die "dcc: failed read status"; - $sock->getline() or die "dcc: failed read multistatus"; + # prefer dccifd to dccproc + if ($self->{dccifd_available}) { + $pgm = 'dccifd'; - my @null = $sock->getlines(); - if (!@null) { - # no facility prefix on this - die "dcc: failed to read header\n"; - } + $sock = $self->dccifd_connect($tag); + if (!$sock) { + $self->{dccifd_available} = 0; + die("dccproc not available") if (!$self->is_dccproc_available()); - # the first line will be the header we want to look at - chomp($response = shift @null); - # but newer versions of DCC fold the header if it's too long... - while (my $v = shift @null) { - last unless ($v =~ s/^\s+/ /); # if this line wasn't folded, stop - chomp $v; - $response .= $v; + # fall back on dccproc if the socket is an orphan from + # a killed dccifd daemon or some other obvious (no timeout) problem + dbg("$tag fall back on dccproc"); } - - dbg("dcc: dccifd got response: %s", $response); - - }); - - $permsgstatus->leave_helper_run_mode(); - - if ($timer->timed_out()) { - dbg("dcc: dccifd check timed out after $timeout secs."); - return; } - if ($err) { - chomp $err; - warn("dcc: dccifd -> check skipped: $err\n"); - return; - } + if ($self->{dccifd_available}) { - if (!defined $response || $response !~ /^X-DCC/) { - dbg("dcc: dccifd check failed - no X-DCC returned: %s", $response); - return; + # send the options and other parameters to the daemon + $client = $envelope->{ip}; + $clientname = $envelope->{rdns}; + if (!defined $client) { + $client = ''; + } else { + $client .= ("\r" . $clientname) if defined $clientname; } + $helo = $envelope->{helo} || ''; + if ($tag ne "dcc:") { + $opts = $self->{dccifd_report_options} + } else { + $opts = $self->{dccifd_lookup_options}; + # only query if there is an X-DCC header + $opts =~ s/grey-off/& query/ if defined $permsgstatus->{dcc_raw_x_dcc}; + } + $sock->print($opts) or die "failed write options\n"; + $sock->print($client . "\n") or die "failed write SMTP client\n"; + $sock->print($helo . "\n") or die "failed write HELO value\n"; + $sock->print("\n") or die "failed write sender\n"; + $sock->print("unknown\n\n") or die "failed write 1 recipient\n"; + $sock->print($$fulltext) or die "failed write mail message\n"; + $sock->shutdown(1) or die "failed socket shutdown: $!"; - $response =~ s/[ \t]\z//; # strip trailing whitespace - $permsgstatus->{dcc_response} = $response; -} + $sock->getline() or die "failed read status\n"; + $sock->getline() or die "failed read multistatus\n"; -sub dccproc_lookup { - my ($self, $permsgstatus, $fulltext, $client) = @_; - my $conf = $self->{main}->{conf}; - my $response; - my %count = (body => 0, fuz1 => 0, fuz2 => 0, rep => 0); - my $timeout = $conf->{dcc_timeout}; + @resp = $sock->getlines(); + die "failed to read dccifd response\n" if !@resp; - $permsgstatus->enter_helper_run_mode(); - - # use a temp file here -- open2() is unreliable, buffering-wise, under spamd + } else { + $pgm = 'dccproc'; + # use a temp file -- open2() is unreliable, buffering-wise, under spamd + # first ensure that we do not hit a stray file from some other filter. + $permsgstatus->delete_fulltext_tmpfile(); my $tmpf = $permsgstatus->create_fulltext_tmpfile($fulltext); - my $pid; - - my $timer = Mail::SpamAssassin::Timeout->new( - { secs => $timeout, deadline => $permsgstatus->{master_deadline} }); - my $err = $timer->run_and_catch(sub { - - local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; - # note: not really tainted, this came from system configuration file - my $path = untaint_file_path($conf->{dcc_path}); - - my $opts = $conf->{dcc_options}; + my $path = $conf->{dcc_path}; + $opts = $conf->{dcc_options}; my @opts = !defined $opts ? () : split(' ',$opts); untaint_var(\@opts); + unshift(@opts, '-w', 'whiteclnt'); + $client = $envelope->{ip}; + if ($client) { + unshift(@opts, '-a', untaint_var($client)); + } else { + # get external relay IP address from Received: header if not available + unshift(@opts, '-R'); + } + if ($tag eq "dcc:") { + # query instead of report if there is an X-DCC header from upstream -+ unshift(@opts, '-Q', 'many') if defined $permsgstatus->{dcc_raw_x_dcc}; ++ unshift(@opts, '-Q') if defined $permsgstatus->{dcc_raw_x_dcc}; + } else { + # learn or report spam + unshift(@opts, '-t', 'many'); + } - unshift(@opts, "-a", - untaint_var($client)) if defined $client && $client ne ''; - - dbg("dcc: opening pipe: %s", - join(' ', $path, "-H", "-x", "0", @opts, "< $tmpf")); + dbg("$tag opening pipe to %s", + join(' ', $path, "-C", "-x", "0", @opts, "<$tmpf")); $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC, - $tmpf, 1, $path, "-H", "-x", "0", @opts); + $tmpf, 1, $path, "-C", "-x", "0", @opts); $pid or die "$!\n"; # read+split avoids a Perl I/O bug (Bug 5985) my($inbuf,$nread,$resp); $resp = ''; while ( $nread=read(DCC,$inbuf,8192) ) { $resp .= $inbuf } defined $nread or die "error reading from pipe: $!"; - my @null = split(/^/m, $resp, -1); undef $resp; + @resp = split(/^/m, $resp, -1); undef $resp; my $errno = 0; close DCC or $errno = $!; proc_status_ok($?,$errno) - or info("dcc: [%s] finished: %s", $pid, exit_status_str($?,$errno)); - - if (!@null) { - # no facility prefix on this - die "failed to read header\n"; - } + or info("$tag [%s] finished: %s", $pid, exit_status_str($?,$errno)); - # the first line will be the header we want to look at - chomp($response = shift @null); - # but newer versions of DCC fold the header if it's too long... - while (my $v = shift @null) { - last unless ($v =~ s/^\s+/ /); # if this line wasn't folded, stop - chomp $v; - $response .= $v; + die "failed to read X-DCC header from dccproc\n" if !@resp; } - - unless (defined($response)) { - # no facility prefix on this - die "no response\n"; # yes, this is possible - } - - dbg("dcc: got response: %s", $response); - }); + if ($pgm eq 'dccproc') { if (defined(fileno(*DCC))) { # still open if ($pid) { - if (kill('TERM',$pid)) { dbg("dcc: killed stale helper [$pid]") } - else { dbg("dcc: killing helper application [$pid] failed: $!") } + if (kill('TERM',$pid)) { + dbg("$tag killed stale dccproc process [$pid]") + } else { + dbg("$tag killing dccproc process [$pid] failed: $!") + } } my $errno = 0; close(DCC) or $errno = $!; - proc_status_ok($?,$errno) - or info("dcc: [%s] terminated: %s", $pid, exit_status_str($?,$errno)); + proc_status_ok($?,$errno) or info("$tag [%s] dccproc terminated: %s", + $pid, exit_status_str($?,$errno)); + } } + $permsgstatus->leave_helper_run_mode(); if ($timer->timed_out()) { @@ -833,204 +904,182 @@ $permsgstatus->leave_helper_run_mode(); if ($timer->timed_out()) { - dbg("dcc: check timed out after $timeout seconds"); - return; + dbg("$tag $pgm timed out after $timeout seconds"); + return (undef, undef); } if ($err) { chomp $err; - if ($err eq "__brokenpipe__ignore__") { - dbg("dcc: check failed: broken pipe"); - } elsif ($err eq "no response") { - dbg("dcc: check failed: no response"); - } else { - warn("dcc: check failed: $err\n"); - } - return; + info("$tag $pgm failed: $err\n"); + return (undef, undef); } - if (!defined($response) || $response !~ /^X-DCC/) { - $response ||= ''; - dbg("dcc: check failed: no X-DCC returned (did you create a map file?): %s", $response); - return; + my ($raw_x_dcc, $cksums) = $self->parse_dcc_response(\@resp); + if (!defined $raw_x_dcc || $raw_x_dcc !~ /^X-DCC/) { + info("$tag instead of X-DCC header, $pgm returned '%s'", $raw_x_dcc); + return (undef, undef); } - - $permsgstatus->{dcc_response} = $response; + dbg("$tag %s responded with '%s'", $pgm, $raw_x_dcc); + return ($raw_x_dcc, $cksums); } -# only supports dccproc right now -sub plugin_report { +# tell DCC server that the message is spam according to SpamAssassin +sub check_post_learn { my ($self, $options) = @_; - return if $options->{report}->{options}->{dont_report_to_dcc}; - $self->get_dcc_interface(); - return if $self->{dcc_disabled}; - - # get the metadata from the message so we can pass the external relay information - $options->{msg}->extract_message_metadata($options->{report}->{main}); - my $client = $options->{msg}->{metadata}->{relays_external}->[0]->{ip}; - if ($self->{dccifd_available}) { - my $clientname = $options->{msg}->{metadata}->{relays_external}->[0]->{rdns}; - my $helo = $options->{msg}->{metadata}->{relays_external}->[0]->{helo} || ""; - if ($client) { - if ($clientname) { - $client = $client . "\r" . $clientname; - } - } else { - $client = "0.0.0.0"; - } - if ($self->dccifd_report($options, $options->{text}, $client, $helo)) { - $options->{report}->{report_available} = 1; - info("reporter: spam reported to DCC"); - $options->{report}->{report_return} = 1; + # learn only if allowed + return if $self->{learn_disabled}; + my $conf = $self->{main}->{conf}; + if (!$conf->{use_dcc}) { + $self->{learn_disabled} = 1; + return; } - else { - info("reporter: could not report spam to DCC via dccifd"); + my $learn_score = $conf->{dcc_learn_score}; + if (!defined $learn_score || $learn_score eq '') { + dbg("dcc: DCC learning not enabled by dcc_learn_score"); + $self->{learn_disabled} = 1; + return; } - } else { - # use temporary file: open2() is unreliable due to buffering under spamd - my $tmpf = $options->{report}->create_fulltext_tmpfile($options->{text}); - if ($self->dcc_report($options, $tmpf, $client)) { - $options->{report}->{report_available} = 1; - info("reporter: spam reported to DCC"); - $options->{report}->{report_return} = 1; + # and if SpamAssassin concluded that the message is spam + # worse than our threshold + my $permsgstatus = $options->{permsgstatus}; + if ($permsgstatus->is_spam()) { + my $score = $permsgstatus->get_score(); + my $required_score = $permsgstatus->get_required_score(); + if ($score < $required_score + $learn_score) { + dbg("dcc: score=%d required_score=%d dcc_learn_score=%d", + $score, $required_score, $learn_score); + return; } - else { - info("reporter: could not report spam to DCC via dccproc"); } - $options->{report}->delete_fulltext_tmpfile(); + + # and if we checked the message + return if (!defined $permsgstatus->{dcc_raw_x_dcc}); + + # and if the DCC server thinks it was not spam + if ($permsgstatus->{dcc_raw_x_dcc} !~ /\b(Body|Fuz1|Fuz2)=\d/) { + dbg("dcc: already known as spam; no need to learn"); + return; } + + # dccsight is faster than dccifd or dccproc if we have checksums, + # which we do not have with dccifd before 1.3.123 + my $old_cksums = $permsgstatus->{dcc_cksums}; + return if ($old_cksums && $self->dccsight_learn($permsgstatus, $old_cksums)); + + # Fall back on dccifd or dccproc without saved checksums or dccsight. + # get_dcc_interface() was called when the message was checked + + # is getting the full text this way kosher? Is get_pristine() public? + my $fulltext = $permsgstatus->{msg}->get_pristine(); + my $envelope = $permsgstatus->{relays_external}->[0]; + my ($raw_x_dcc, $cksums) = $self->ask_dcc("dcc: learn:", $permsgstatus, + \$fulltext, $envelope); + dbg("dcc: learned as spam") if defined $raw_x_dcc; } -sub dccifd_report { - my ($self, $options, $fulltext, $client, $helo) = @_; - my $conf = $self->{main}->{conf}; - my $timeout = $conf->{dcc_timeout}; - # instead of header use whatever the report option is - my $opts = $conf->{dccifd_options}; - my @opts = !defined $opts ? () : split(' ',$opts); +sub dccsight_learn { + my ($self, $permsgstatus, $old_cksums) = @_; + my ($raw_x_dcc, $new_cksums); + + return 0 if !$old_cksums; + + my $dccsight = $self->dcc_pgm_path('dccsight'); + if (!$dccsight) { + info("dcc: cannot find dccsight") if $dccsight eq ''; + return 0; + } - $options->{report}->enter_helper_run_mode(); - my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout }); + $permsgstatus->enter_helper_run_mode(); - my $err = $timer->run_and_catch(sub { + # use a temp file here -- open2() is unreliable, buffering-wise, under spamd + # ensure that we do not hit a stray file from some other filter. + $permsgstatus->delete_fulltext_tmpfile(); + my $tmpf = $permsgstatus->create_fulltext_tmpfile(\$old_cksums); + my $pid; + my $timeout = $self->{main}->{conf}->{dcc_timeout}; + my $timer = Mail::SpamAssassin::Timeout->new( + { secs => $timeout, deadline => $permsgstatus->{master_deadline} }); + my $err = $timer->run_and_catch(sub { local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; - my $sock = $self->dccifd_connect(); - $sock or die "report: failed to connect to a dccifd socket"; + dbg("dcc: opening pipe to %s", + join(' ', $dccsight, "-t", "many", "<$tmpf")); - # send the options and other parameters to the daemon - $sock->print("spam " . join(" ",@opts) . "\n") - or die "report: dccifd failed write"; # options - $sock->print($client . "\n") - or die "report: dccifd failed write"; # client - $sock->print($helo . "\n") - or die "report: dccifd failed write"; # HELO value - $sock->print("\n") - or die "report: dccifd failed write"; # sender - $sock->print("unknown\r\n") - or die "report: dccifd failed write"; # recipients - $sock->print("\n") - or die "report: dccifd failed write"; # recipients + $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC, + $tmpf, 1, $dccsight, "-t", "many"); + $pid or die "$!\n"; - $sock->print($$fulltext) or die "report: dccifd failed write"; + # read+split avoids a Perl I/O bug (Bug 5985) + my($inbuf,$nread,$resp); $resp = ''; + while ( $nread=read(DCC,$inbuf,8192) ) { $resp .= $inbuf } + defined $nread or die "error reading from pipe: $!"; + my @resp = split(/^/m, $resp, -1); undef $resp; - $sock->shutdown(1) or die "report: dccifd failed socket shutdown: $!"; + my $errno = 0; close DCC or $errno = $!; + proc_status_ok($?,$errno) + or info("dcc: [%s] finished: %s", $pid, exit_status_str($?,$errno)); - $sock->getline() or die "report: dccifd failed read status"; - $sock->getline() or die "report: dccifd failed read multistatus"; + die "dcc: failed to read learning response\n" if !@resp; - my @ignored = $sock->getlines(); + ($raw_x_dcc, $new_cksums) = $self->parse_dcc_response(\@resp); }); - $options->{report}->leave_helper_run_mode(); + if (defined(fileno(*DCC))) { # still open + if ($pid) { + if (kill('TERM',$pid)) { + dbg("dcc: killed stale dccsight process [$pid]") + } else { + dbg("dcc: killing stale dccsight process [$pid] failed: $!") } + } + my $errno = 0; close(DCC) or $errno = $!; + proc_status_ok($?,$errno) or info("dcc: dccsight [%s] terminated: %s", + $pid, exit_status_str($?,$errno)); + } + $permsgstatus->delete_fulltext_tmpfile(); + $permsgstatus->leave_helper_run_mode(); if ($timer->timed_out()) { - dbg("reporter: DCC report via dccifd timed out after $timeout secs."); + dbg("dcc: dccsight timed out after $timeout seconds"); return 0; } if ($err) { chomp $err; - if ($err eq "__brokenpipe__ignore__") { - dbg("reporter: DCC report via dccifd failed: broken pipe"); - } else { - warn("reporter: DCC report via dccifd failed: $err\n"); - } + info("dcc: dccsight failed: $err\n"); return 0; } + if ($raw_x_dcc) { + dbg("dcc: learned response: %s", $raw_x_dcc); return 1; -} - -sub dcc_report { - my ($self, $options, $tmpf, $client) = @_; - my $conf = $self->{main}->{conf}; - my $timeout = $options->{report}->{conf}->{dcc_timeout}; - - # note: not really tainted, this came from system configuration file - my $path = untaint_file_path($options->{report}->{conf}->{dcc_path}); - my $opts = $conf->{dcc_options}; - my @opts = !defined $opts ? () : split(' ',$opts); - untaint_var(\@opts); - - # get the metadata from the message so we can pass the external relay info - - unshift(@opts, "-a", - untaint_var($client)) if defined $client && $client ne ''; - - my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout }); - - $options->{report}->enter_helper_run_mode(); - my $err = $timer->run_and_catch(sub { - - local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" }; - - dbg("report: opening pipe: %s", - join(' ', $path, "-H", "-t", "many", "-x", "0", @opts, "< $tmpf")); - - my $pid = Mail::SpamAssassin::Util::helper_app_pipe_open(*DCC, - $tmpf, 1, $path, "-H", "-t", "many", "-x", "0", @opts); - $pid or die "$!\n"; + } - my($inbuf,$nread,$nread_all); $nread_all = 0; - # response is ignored, just check its existence - while ( $nread=read(DCC,$inbuf,8192) ) { $nread_all += $nread } - defined $nread or die "error reading from pipe: $!"; + return 0; +} - dbg("dcc: empty response") if $nread_all < 1; +sub plugin_report { + my ($self, $options) = @_; - my $errno = 0; close DCC or $errno = $!; - # closing a pipe also waits for the process executing on the pipe to - # complete, no need to explicitly call waitpid - # my $child_stat = waitpid($pid,0) > 0 ? $? : undef; - proc_status_ok($?,$errno) - or die "dcc: reporter error: ".exit_status_str($?,$errno)."\n"; - }); - $options->{report}->leave_helper_run_mode(); + return if $options->{report}->{options}->{dont_report_to_dcc}; + $self->get_dcc_interface(); + return if $self->{dcc_disabled}; - if ($timer->timed_out()) { - dbg("reporter: DCC report via dccproc timed out after $timeout seconds"); - return 0; - } + # get the metadata from the message so we can report the external relay + $options->{msg}->extract_message_metadata($options->{report}->{main}); + my $envelope = $options->{msg}->{metadata}->{relays_external}->[0]; + my ($raw_x_dcc, $cksums) = $self->ask_dcc("reporter:", $options->{report}, + $options->{text}, $envelope); - if ($err) { - chomp $err; - if ($err eq "__brokenpipe__ignore__") { - dbg("reporter: DCC report via dccproc failed: broken pipe"); + if (defined $raw_x_dcc) { + $options->{report}->{report_available} = 1; + info("reporter: spam reported to DCC"); + $options->{report}->{report_return} = 1; } else { - warn("reporter: DCC report via dccproc failed: $err\n"); + info("reporter: could not report spam to DCC"); } - return 0; - } - - return 1; } 1; - -=back - -=cut Property changes on: head/mail/p5-Mail-SpamAssassin/files/patch-bug6698 ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.1 \ No newline at end of property +1.2 \ No newline at end of property Index: head/mail/p5-Mail-SpamAssassin/pkg-plist =================================================================== --- head/mail/p5-Mail-SpamAssassin/pkg-plist (revision 288061) +++ head/mail/p5-Mail-SpamAssassin/pkg-plist (revision 288062) @@ -1,153 +1,153 @@ @stopdaemon sa-spamd bin/sa-awl bin/sa-check_spamd bin/sa-compile bin/sa-learn bin/sa-update bin/spamassassin bin/spamc bin/spamd -@unexec rm -rf %D/etc/mail/spamassassin/sa-update-keys || true +@unexec rm -rf %D/etc/mail/spamassassin/sa-update-keys etc/mail/spamassassin/local.cf.sample @unexec if cmp -s %B/init.pre.sample %B/init.pre; then rm -f %B/init.pre; fi etc/mail/spamassassin/init.pre.sample @exec [ -f %B/init.pre ] || cp %B/%f %B/init.pre @unexec if cmp -s %B/v310.pre.sample %B/v310.pre; then rm -f %B/v310.pre; fi etc/mail/spamassassin/v310.pre.sample @exec [ -f %B/v310.pre ] || cp %B/%f %B/v310.pre @unexec if cmp -s %B/v312.pre.sample %B/v312.pre; then rm -f %B/v312.pre; fi etc/mail/spamassassin/v312.pre.sample @exec [ -f %B/v312.pre ] || cp %B/%f %B/v312.pre @unexec if cmp -s %B/v320.pre.sample %B/v320.pre; then rm -f %B/v320.pre; fi etc/mail/spamassassin/v320.pre.sample @exec [ -f %B/v320.pre ] || cp %B/%f %B/v320.pre @unexec if cmp -s %B/v330.pre.sample %B/v330.pre; then rm -f %B/v330.pre;fi etc/mail/spamassassin/v330.pre.sample @exec [ -f %B/v330.pre ] || cp %B/%f %B/v330.pre include/libspamc.h lib/libspamc.so lib/libspamc.so.0 %%SSL%%lib/libsslspamc.so %%SSL%%lib/libsslspamc.so.0 %%SITE_PERL%%/Mail/SpamAssassin.pm %%SITE_PERL%%/Mail/SpamAssassin/AICache.pm %%SITE_PERL%%/Mail/SpamAssassin/ArchiveIterator.pm %%SITE_PERL%%/Mail/SpamAssassin/AsyncLoop.pm %%SITE_PERL%%/Mail/SpamAssassin/AutoWhitelist.pm %%SITE_PERL%%/Mail/SpamAssassin/Bayes.pm %%SITE_PERL%%/Mail/SpamAssassin/Bayes/CombineChi.pm %%SITE_PERL%%/Mail/SpamAssassin/Bayes/CombineNaiveBayes.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore/BDB.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore/DBM.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore/MySQL.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore/PgSQL.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore/SDBM.pm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore/SQL.pm %%SITE_PERL%%/Mail/SpamAssassin/Client.pm %%SITE_PERL%%/Mail/SpamAssassin/Conf.pm %%SITE_PERL%%/Mail/SpamAssassin/Conf/LDAP.pm %%SITE_PERL%%/Mail/SpamAssassin/Conf/Parser.pm %%SITE_PERL%%/Mail/SpamAssassin/Conf/SQL.pm %%SITE_PERL%%/Mail/SpamAssassin/Constants.pm %%SITE_PERL%%/Mail/SpamAssassin/DBBasedAddrList.pm %%SITE_PERL%%/Mail/SpamAssassin/Dns.pm %%SITE_PERL%%/Mail/SpamAssassin/DnsResolver.pm %%SITE_PERL%%/Mail/SpamAssassin/HTML.pm %%SITE_PERL%%/Mail/SpamAssassin/Locales.pm %%SITE_PERL%%/Mail/SpamAssassin/Locker.pm %%SITE_PERL%%/Mail/SpamAssassin/Locker/Flock.pm %%SITE_PERL%%/Mail/SpamAssassin/Locker/UnixNFSSafe.pm %%SITE_PERL%%/Mail/SpamAssassin/Locker/Win32.pm %%SITE_PERL%%/Mail/SpamAssassin/Logger.pm %%SITE_PERL%%/Mail/SpamAssassin/Logger/File.pm %%SITE_PERL%%/Mail/SpamAssassin/Logger/Stderr.pm %%SITE_PERL%%/Mail/SpamAssassin/Logger/Syslog.pm %%SITE_PERL%%/Mail/SpamAssassin/MailingList.pm %%SITE_PERL%%/Mail/SpamAssassin/Message.pm %%SITE_PERL%%/Mail/SpamAssassin/Message/Metadata.pm %%SITE_PERL%%/Mail/SpamAssassin/Message/Metadata/Received.pm %%SITE_PERL%%/Mail/SpamAssassin/Message/Node.pm %%SITE_PERL%%/Mail/SpamAssassin/NetSet.pm %%SITE_PERL%%/Mail/SpamAssassin/PerMsgLearner.pm %%SITE_PERL%%/Mail/SpamAssassin/PerMsgStatus.pm %%SITE_PERL%%/Mail/SpamAssassin/PersistentAddrList.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/ASN.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/AWL.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/AccessDB.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/AntiVirus.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/AutoLearnThreshold.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Bayes.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/BodyEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/BodyRuleBaseExtractor.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Check.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/DCC.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/DKIM.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/DNSEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/FreeMail.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/HTMLEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/HTTPSMismatch.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Hashcash.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/HeaderEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/ImageInfo.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/MIMEEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/MIMEHeader.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/OneLineBodyRuleType.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/PhishTag.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Pyzor.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Razor2.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/RelayCountry.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/RelayEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/ReplaceTags.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Reuse.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Rule2XSBody.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/SPF.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Shortcircuit.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/SpamCop.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/Test.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/TextCat.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/URIDNSBL.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/URIDetail.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/URIEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/VBounce.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/WLBLEval.pm %%SITE_PERL%%/Mail/SpamAssassin/Plugin/WhiteListSubject.pm %%SITE_PERL%%/Mail/SpamAssassin/PluginHandler.pm %%SITE_PERL%%/Mail/SpamAssassin/Reporter.pm %%SITE_PERL%%/Mail/SpamAssassin/SQLBasedAddrList.pm %%SITE_PERL%%/Mail/SpamAssassin/SpamdForkScaling.pm %%SITE_PERL%%/Mail/SpamAssassin/SubProcBackChannel.pm %%SITE_PERL%%/Mail/SpamAssassin/Timeout.pm %%SITE_PERL%%/Mail/SpamAssassin/Util.pm %%SITE_PERL%%/Mail/SpamAssassin/Util/DependencyInfo.pm %%SITE_PERL%%/Mail/SpamAssassin/Util/Progress.pm %%SITE_PERL%%/Mail/SpamAssassin/Util/RegistrarBoundaries.pm %%SITE_PERL%%/Mail/SpamAssassin/Util/ScopedTimer.pm %%SITE_PERL%%/Mail/SpamAssassin/Util/TieOneStringHash.pm %%SITE_PERL%%/%%PERL_ARCH%%/auto/Mail/SpamAssassin/.packlist %%SITE_PERL%%/spamassassin-run.pod %%DATADIR%%/languages %%DATADIR%%/sa-update-pubkey.txt %%DATADIR%%/user_prefs.template -@unexec rm -rf /var/lib/spamassassin/2* || true +@unexec rm -rf /var/lib/spamassassin/2* @unexec rmdir /var/lib/spamassassin 2>/dev/null || true @unexec rmdir /var/lib 2>/dev/null || true @unexec rmdir /var/db/spamassassin 2>/dev/null || true @dirrm %%DATADIR%% @dirrm %%SITE_PERL%%/%%PERL_ARCH%%/auto/Mail/SpamAssassin @dirrmtry %%SITE_PERL%%/%%PERL_ARCH%%/auto/Mail @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Util @dirrmtry %%SITE_PERL%%/Mail/SpamAssassin/Plugin @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Message/Metadata @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Message @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Logger @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Locker @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Conf @dirrm %%SITE_PERL%%/Mail/SpamAssassin/BayesStore @dirrm %%SITE_PERL%%/Mail/SpamAssassin/Bayes @dirrm %%SITE_PERL%%/Mail/SpamAssassin @dirrmtry %%SITE_PERL%%/Mail @dirrmtry etc/mail/spamassassin @dirrmtry etc/mail -@unexec rm -rf /var/run/spamd || true +@unexec rm -rf /var/run/spamd Property changes on: head/mail/p5-Mail-SpamAssassin/pkg-plist ___________________________________________________________________ Modified: cvs2svn:cvs-rev ## -1 +1 ## -1.47 \ No newline at end of property +1.48 \ No newline at end of property