diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dfffe9f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI Tests + +on: + push: + pull_request: + +jobs: + test: + strategy: + matrix: + os: ["ubuntu-latest"] + perl: ["5.16", "5.32"] + fail-fast: false + runs-on: ubuntu-latest + services: + redis: + image: redis + ports: + - 6379:6379 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 10 + fetch-tags: true + - uses: shogo82148/actions-setup-perl@v1 + with: + perl-version: ${{ matrix.perl }} + - run: cpanm --installdeps -n -f . + - run: cpanm --installdeps -n -f Mail::SPF Mail::DMARC GeoIP2 ClamAV::Client Redis + - run: prove -lv t \ No newline at end of file diff --git a/.perltidyrc b/.perltidyrc index a2342eb..815ab6e 100644 --- a/.perltidyrc +++ b/.perltidyrc @@ -1,5 +1,5 @@ --i=4 # 4 space indentation (we used to use 2; in the future we'll use 4) +-i=4 # 4 space indentation -ci=2 # continuation indention -bbt=0 # open code block curly braces diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e980c41..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: perl -perl: - - "5.18" - - "5.16" - - "5.8" - -before_install: - - cpanm -n Devel::Cover::Report::Coveralls - -after_script: - - cover -test -report coveralls diff --git a/MANIFEST b/MANIFEST index 89a1018..a38bb01 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,8 +1,8 @@ .gitignore -.travis.yml bin/geolite-mirror-simple.pl bin/install_deps.pl Changes +config.sample/add_geoip_headers config.sample/badhelo config.sample/badmailfrom config.sample/badrcptto @@ -12,6 +12,7 @@ config.sample/dnsbl_zones config.sample/flat_auth_pw config.sample/invalid_resolvable_fromhost config.sample/IP +config.sample/karma_tlds config.sample/log2sql config.sample/logging config.sample/loglevel @@ -43,6 +44,9 @@ lib/Qpsmtpd/Config.pm lib/Qpsmtpd/ConfigServer.pm lib/Qpsmtpd/Connection.pm lib/Qpsmtpd/Constants.pm +lib/Qpsmtpd/DB.pm +lib/Qpsmtpd/DB/File/DBM.pm +lib/Qpsmtpd/DB/Redis.pm lib/Qpsmtpd/DSN.pm lib/Qpsmtpd/Plugin.pm lib/Qpsmtpd/Postfix.pm @@ -67,6 +71,7 @@ META.yml Module meta-data (added by MakeMaker) plugins/auth/auth_checkpassword plugins/auth/auth_cvm_unix_local plugins/auth/auth_flat_file +plugins/auth/auth_imap plugins/auth/auth_ldap_bind plugins/auth/auth_vpopmail plugins/auth/auth_vpopmail_sql @@ -154,8 +159,10 @@ run.tcpserver STATUS t/addresses.t t/auth.t +t/config/add_geoip_headers t/config/badhelo t/config/badrcptto +t/config/content_log_enabled t/config/dnsbl_allow t/config/dnsbl_zones t/config/flat_auth_pw @@ -174,6 +181,7 @@ t/misc.t t/plugin_tests.t t/plugin_tests/auth/auth_checkpassword t/plugin_tests/auth/auth_flat_file +t/plugin_tests/auth/auth_imap t/plugin_tests/auth/auth_vpopmail t/plugin_tests/auth/auth_vpopmail_sql t/plugin_tests/auth/auth_vpopmaild @@ -182,11 +190,12 @@ t/plugin_tests/auth/authnull t/plugin_tests/badmailfrom t/plugin_tests/badmailfromto t/plugin_tests/badrcptto +t/plugin_tests/content_log t/plugin_tests/count_unrecognized_commands t/plugin_tests/dmarc t/plugin_tests/dnsbl -t/plugin_tests/dspam t/plugin_tests/earlytalker +t/plugin_tests/fcrdns t/plugin_tests/greylisting t/plugin_tests/headers t/plugin_tests/helo @@ -202,6 +211,10 @@ t/plugin_tests/virus/clamdscan t/qpsmtpd-address.t t/qpsmtpd-base.t t/qpsmtpd-config.t +t/qpsmtpd-db-file-dbm.t +t/qpsmtpd-db-redis.t +t/qpsmtpd-db.t +t/qpsmtpd-plugin.t t/qpsmtpd-smtp.t t/qpsmtpd.t t/rset.t diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 6cbde86..f281243 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -14,6 +14,7 @@ output/.* ^sqlite/ ^output/ ^tmp/ +^t/tmp/ ^blib/ ^blibdirs$ ^Makefile$ @@ -36,3 +37,4 @@ packaging ^supervise/ ^ssl/ ^t/config/greylist +^.github \ No newline at end of file diff --git a/Makefile.PL b/Makefile.PL index 1bdf98b..125b8c7 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -22,19 +22,20 @@ WriteMakefile( 'Test::More' => 0, 'Test::Output' => 0, # modules for specific features - 'Mail::DKIM' => 0.40, - 'File::Tail' => 0, # log/summarize, log/watch - 'Time::TAI64' => 0, # log2sql -# 'DBI' => 0, # auth_vpopmail_sql and -# 'DBD::mysql' => 0, # log2sql -# 'DBIx::Simple' => 0, # log2sql -# modules that cause Travis build tests to fail -# 'Mail::SpamAssassin' => 0, -# 'GeoIP2' => 2, -# 'Geo::IP' => 1, -# 'Math::Complex' => 0, # geodesic distance in Geo::IP -# 'PerlIO::gzip' => 0, # gunzip GeoIP databases -# 'Mail::SPF' => 0, + 'Mail::SPF' => 1, + 'Mail::DKIM' => 0.40, + 'Mail::DMARC' => 0, + 'File::Tail' => 0, # log/summarize, log/watch + 'Time::TAI64' => 0, # log2sql + 'Redis' => 0, +# 'DBI' => 0, # auth_vpopmail_sql and +# 'DBD::mysql' => 0, # log2sql +# 'DBIx::Simple' => 0, # log2sql + 'Geo::IP' => 1, + 'Mail::SpamAssassin' => 0, + 'Math::Complex' => 0, # geodesic distance in Geo::IP + 'PerlIO::gzip' => 0, # gunzip GeoIP databases + 'File::NFSLock' => 0, }, ABSTRACT => 'Flexible smtpd daemon written in Perl', AUTHOR => 'Ask Bjoern Hansen ', diff --git a/README.md b/README.md index 9e37987..cf34eb9 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,11 @@ run the following command in the /home/smtpd/ directory. git clone git://github.com/smtpd/qpsmtpd.git -Beware that the master branch might be unstable and unsuitable for anything +Beware that the master branch might be unsuitable for anything but development, so you might want to get a specific release, for example (after running git clone): - git checkout -b local_branch v0.93 + git checkout -b local_branch v1.00 chmod o+t ~smtpd/qpsmtpd/ (or whatever directory you installed qpsmtpd in) to make supervise start the log process. @@ -91,9 +91,8 @@ information about what's missing) to the mailinglist or a PR to github. # Better Performance -For better performance we recommend using "qpsmtpd-forkserver" or -running qpsmtpd under Apache 2.x. If you need extremely high -concurrency use [Haraka](http://haraka.github.io/). +For better performance we recommend using "qpsmtpd-forkserver". If +you need extremely high concurrency use [Haraka](http://haraka.github.io/). # Plugins diff --git a/bin/install_deps.pl b/bin/install_deps.pl index bb2f7cb..e3487f1 100755 --- a/bin/install_deps.pl +++ b/bin/install_deps.pl @@ -29,9 +29,6 @@ use English qw( -no_match_vars ); my $apps = [ { app => 'daemontools', info => { } }, { app => 'ucspi-tcp', info => { } }, -# { app => 'dspam', info => { } }, -# { app => 'mysql-server-55', info => { port => 'mysql55-server', dport=>'mysql5', yum =>'mysql-server'} }, -# { app => 'apache22' , info => { port => 'apache22', dport=>'', yum => 'httpd' } }, ]; $EUID == 0 or die "You will have better luck if you run me as root.\n"; diff --git a/lib/Qpsmtpd/DB/File/DBM.pm b/lib/Qpsmtpd/DB/File/DBM.pm index aa9cc94..7c92503 100644 --- a/lib/Qpsmtpd/DB/File/DBM.pm +++ b/lib/Qpsmtpd/DB/File/DBM.pm @@ -4,7 +4,7 @@ use warnings; use parent 'Qpsmtpd::DB'; -BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File) } +BEGIN { @AnyDBM_File::ISA = qw(DB_File GDBM_File NDBM_File SDBM_File ODBM_File) } use AnyDBM_File; use Fcntl qw(:DEFAULT :flock LOCK_EX LOCK_NB); diff --git a/lib/Qpsmtpd/SMTP.pm b/lib/Qpsmtpd/SMTP.pm index d145190..06c8875 100644 --- a/lib/Qpsmtpd/SMTP.pm +++ b/lib/Qpsmtpd/SMTP.pm @@ -31,7 +31,7 @@ sub new { my $self = bless({args => \%args}, $class); - # this list of valid commands should probably be a method or a set of methods + # this list of valid commands should probably be a method or a set of methods $self->{_commands} = {map { $_ => '' } qw(ehlo helo rset mail rcpt data help vrfy noop quit)}; @@ -712,8 +712,6 @@ sub data_respond { $header->extract(\@headers); -#$header->add("X-SMTPD", "qpsmtpd/".$self->version.", http://smtpd.github.io/qpsmtpd/"); - $buffer = ''; $self->transaction->header($header); diff --git a/plugins/dmarc b/plugins/dmarc index d37a9b4..024f786 100644 --- a/plugins/dmarc +++ b/plugins/dmarc @@ -140,7 +140,7 @@ sub check_dmarc { return $self->get_reject( $@ ); }; -#$self->log(LOGINFO, "result: " . Dumper( $dmarc ) ); + #$self->log(LOGINFO, "result: " . Dumper( $dmarc ) ); my $pol; eval { $pol = $dmarc->result->published; }; diff --git a/plugins/ident/geoip b/plugins/ident/geoip index f5dfa14..1ab6cd0 100644 --- a/plugins/ident/geoip +++ b/plugins/ident/geoip @@ -162,10 +162,10 @@ sub load_geoip1 { $self->open_geoip_db(); -# Note that opening the GeoIP DB only in register has caused problems before: -# https://github.com/smtpd/qpsmtpd/commit/29ea9516806e9a8ca6519fcf987dbd684793ebdd#plugins/ident/geoip -# Opening the DB anew for every connection is horribly inefficient. -# Instead, attempt to reopen upon connect if the DB connection fails. + # Note that opening the GeoIP DB only in register has caused problems before: + # https://github.com/smtpd/qpsmtpd/commit/29ea9516806e9a8ca6519fcf987dbd684793ebdd#plugins/ident/geoip + # Opening the DB anew for every connection is horribly inefficient. + # Instead, attempt to reopen upon connect if the DB connection fails. $self->init_my_country_code(); $self->register_hook('connect', 'geoip_lookup'); diff --git a/t/addresses.t b/t/addresses.t index 09272ba..9c8a2ce 100644 --- a/t/addresses.t +++ b/t/addresses.t @@ -7,26 +7,17 @@ use_ok('Test::Qpsmtpd'); ok(my ($smtpd, $conn) = Test::Qpsmtpd->new_conn(), "get new connection"); is(($smtpd->command('EHLO localhost'))[0], 250, 'EHLO localhost'); -is(($smtpd->command('MAIL FROM:'))[0], - 250, 'MAIL FROM:'); -is($smtpd->transaction->sender->address, 'ask@perl.org', - 'got the right sender'); +is(($smtpd->command('MAIL FROM:'))[0], 250, 'MAIL FROM:'); +is($smtpd->transaction->sender->address, 'ask@perl.org', 'got the right sender'); -is(($smtpd->command('MAIL FROM:'))[0], - 250, 'MAIL FROM:'); -is($smtpd->transaction->sender->address, - 'ask @perl.org', - 'got the right sender'); +is(($smtpd->command('MAIL FROM:'))[0], 250, 'MAIL FROM:'); +is($smtpd->transaction->sender->address, 'ask @perl.org', 'got the right sender'); -is(($smtpd->command('MAIL FROM:ask@perl.org'))[0], - 250, 'MAIL FROM:ask@perl.org'); -is($smtpd->transaction->sender->format, - '', 'got the right sender'); +is(($smtpd->command('MAIL FROM:ask@perl.org'))[0], 250, 'MAIL FROM:ask@perl.org'); +is($smtpd->transaction->sender->format, '', 'got the right sender'); -is(($smtpd->command('MAIL FROM:ask@[1.2.3.4]'))[0], - 250, 'MAIL FROM:ask@[1.2.3.4]'); -is($smtpd->transaction->sender->format, - '', 'got the right sender'); +is(($smtpd->command('MAIL FROM:ask@[1.2.3.4]'))[0], 250, 'MAIL FROM:ask@[1.2.3.4]'); +is($smtpd->transaction->sender->format, '', 'got the right sender'); my $command = 'MAIL FROM: SIZE=1230'; is(($smtpd->command($command))[0], 250, $command); diff --git a/t/config/plugins b/t/config/plugins index 038bd94..4243c84 100644 --- a/t/config/plugins +++ b/t/config/plugins @@ -22,7 +22,7 @@ content_log hosts_allow # information plugins -ident/geoip +# ident/geoip ident/p0f /tmp/.p0f_socket version 3 connection_time fcrdns @@ -37,7 +37,7 @@ parse_addr_withhelo quit_fortune # tls should load before count_unrecognized_commands #tls -earlytalker +# earlytalker count_unrecognized_commands 4 relay @@ -66,7 +66,7 @@ rcpt_ok headers days 5 reject_type temp require From,Date #domainkeys dkim -dmarc +# dmarc # content filters virus/klez_filter @@ -83,11 +83,11 @@ spamassassin # spamassassin reject_threshold 20 munge_subject_threshold 10 # dspam must run after spamassassin for the learn_from_sa feature to work -dspam learn_from_sa 7 reject 1 +# dspam learn_from_sa 7 reject 1 # run the clamav virus checking plugin virus/clamav -virus/clamdscan +# virus/clamdscan # You must enable a queue plugin - see the options in plugins/queue/ - for example: diff --git a/t/plugin_tests/dspam b/t/plugin_tests/dspam deleted file mode 100644 index 93729de..0000000 --- a/t/plugin_tests/dspam +++ /dev/null @@ -1,96 +0,0 @@ -#!perl -w - -use strict; -use warnings; - -use Mail::Header; -use Qpsmtpd::Constants; - -my $r; - -sub register_tests { - my $self = shift; - - $self->register_test('test_get_dspam_results'); - $self->register_test('test_log_and_return'); - $self->register_test('test_reject_type'); -} - -sub test_log_and_return { - my $self = shift; - - my $transaction = $self->qp->transaction; - - # reject not set - $self->{_args}{reject} = undef; - $transaction->notes('dspam', { class=> 'Spam', probability => .99, confidence=>1 } ); - ($r) = $self->log_and_return( $transaction ); - cmp_ok( $r, '==', DECLINED, "($r)"); - - # reject exceeded - $self->{_args}{reject} = .95; - $transaction->notes('dspam', { class=> 'Spam', probability => .99, confidence=>1 } ); - ($r) = $self->log_and_return( $transaction ); - cmp_ok( $r, '==', DENY, "($r)"); - - # below reject threshold - $transaction->notes('dspam', { class=> 'Spam', probability => .94, confidence=>1 } ); - ($r) = $self->log_and_return( $transaction ); - cmp_ok( $r, '==', DECLINED, "($r)"); - - # requires agreement - $self->{_args}{reject} = 'agree'; - $transaction->notes('spamassassin', { is_spam => 'Yes', score => 25 } ); - $transaction->notes('dspam', { class=> 'Spam', probability => .90, confidence=>1 } ); - ($r) = $self->log_and_return( $transaction ); - cmp_ok( $r, '==', DENY, "($r)"); - - # requires agreement - $transaction->notes('spamassassin', { is_spam => 'No', score => 15 } ); - $transaction->notes('dspam', { class=> 'Spam', probability => .96, confidence=>1 } ); - ($r) = $self->log_and_return( $transaction ); - cmp_ok( $r, '==', DECLINED, "($r)"); - - # requires agreement - $transaction->notes('spamassassin', { is_spam => 'Yes', score => 10 } ); - $transaction->notes('dspam', { class=> 'Innocent', probability => .96, confidence=>1 } ); - ($r) = $self->log_and_return( $transaction ); - cmp_ok( $r, '==', DECLINED, "($r)"); -} - -sub test_get_dspam_results { - my $self = shift; - - my $transaction = $self->qp->transaction; - my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE"); - $transaction->header( $header ); - - my @dspam_sample_headers = ( - 'Innocent, probability=0.0000, confidence=0.69', - 'Innocent, probability=0.0000, confidence=0.85', - 'Innocent, probability=0.0023, confidence=1.00', - 'Spam, probability=1.0000, confidence=0.87', - 'Spam, probability=1.0000, confidence=0.99', - 'Whitelisted', - ); - - foreach my $header ( @dspam_sample_headers ) { - $transaction->header->delete('X-DSPAM-Result'); - $transaction->header->add('X-DSPAM-Result', $header); - my $r = $self->get_dspam_results($transaction); - ok( ref $r, "r: ($header)" ); - } -} - -sub test_reject_type { - my $self = shift; - - $self->{_args}{reject_type} = undef; - cmp_ok( $self->get_reject_type(), '==', DENY, "default"); - - $self->{_args}{reject_type} = 'temp'; - cmp_ok( $self->get_reject_type(), '==', DENYSOFT, "defer"); - - $self->{_args}{reject_type} = 'disconnect'; - cmp_ok( $self->get_reject_type(), '==', DENY_DISCONNECT, "disconnect"); -}