first
This commit is contained in:
commit
5aa7d034f7
3292 changed files with 465160 additions and 0 deletions
47
data/conf/clamav/clamd.conf
Executable file
47
data/conf/clamav/clamd.conf
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#Debug true
|
||||
#LogFile /dev/null
|
||||
LogTime yes
|
||||
LogClean yes
|
||||
ExtendedDetectionInfo yes
|
||||
PidFile /run/clamav/clamd.pid
|
||||
OfficialDatabaseOnly no
|
||||
LocalSocket /run/clamav/clamd.sock
|
||||
TCPSocket 3310
|
||||
StreamMaxLength 25M
|
||||
MaxThreads 10
|
||||
ReadTimeout 10
|
||||
CommandReadTimeout 3
|
||||
SendBufTimeout 200
|
||||
MaxQueue 80
|
||||
IdleTimeout 20
|
||||
SelfCheck 3600
|
||||
User clamav
|
||||
Foreground yes
|
||||
DetectPUA yes
|
||||
# See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md
|
||||
#ExcludePUA NetTool
|
||||
#ExcludePUA PWTool
|
||||
#IncludePUA Spy
|
||||
#IncludePUA Scanner
|
||||
#IncludePUA RAT
|
||||
HeuristicAlerts yes
|
||||
ScanOLE2 yes
|
||||
AlertOLE2Macros no
|
||||
ScanPDF yes
|
||||
ScanSWF yes
|
||||
ScanXMLDOCS yes
|
||||
ScanHWP3 yes
|
||||
ScanMail yes
|
||||
PhishingSignatures no
|
||||
PhishingScanURLs no
|
||||
HeuristicScanPrecedence yes
|
||||
ScanHTML yes
|
||||
ScanArchive yes
|
||||
MaxScanSize 50M
|
||||
MaxFileSize 25M
|
||||
MaxRecursion 5
|
||||
MaxFiles 200
|
||||
Bytecode yes
|
||||
BytecodeSecurity TrustSigned
|
||||
BytecodeTimeout 1000
|
||||
ConcurrentDatabaseReload no
|
||||
19
data/conf/clamav/freshclam.conf
Executable file
19
data/conf/clamav/freshclam.conf
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#UpdateLogFile /dev/console
|
||||
LogTime yes
|
||||
PidFile /run/clamav/freshclam.pid
|
||||
DatabaseOwner clamav
|
||||
DNSDatabaseInfo current.cvd.clamav.net
|
||||
DatabaseMirror db.uk.clamav.net
|
||||
DatabaseMirror db.nl.clamav.net
|
||||
DatabaseMirror db.fr.clamav.net
|
||||
DatabaseMirror db.ch.clamav.net
|
||||
MaxAttempts 4
|
||||
ScriptedUpdates yes
|
||||
Checks 6
|
||||
NotifyClamd /etc/clamav/clamd.conf
|
||||
Foreground yes
|
||||
ConnectTimeout 20
|
||||
ReceiveTimeout 20
|
||||
TestDatabases yes
|
||||
Bytecode yes
|
||||
|
||||
14
data/conf/dovecot/conf.d/fts.conf
Normal file
14
data/conf/dovecot/conf.d/fts.conf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Autogenerated by mailcow
|
||||
plugin {
|
||||
fts = solr
|
||||
fts_autoindex = yes
|
||||
fts_autoindex_exclude = \Junk
|
||||
fts_autoindex_exclude2 = \Trash
|
||||
fts_solr = url=http://solr:8983/solr/dovecot-fts/
|
||||
|
||||
fts_tokenizers = generic email-address
|
||||
fts_tokenizer_generic = algorithm=simple
|
||||
|
||||
fts_filters = normalizer-icu snowball stopwords
|
||||
fts_filters_en = lowercase snowball english-possessive stopwords
|
||||
}
|
||||
309
data/conf/dovecot/dovecot.conf
Executable file
309
data/conf/dovecot/dovecot.conf
Executable file
|
|
@ -0,0 +1,309 @@
|
|||
# --------------------------------------------------------------------------
|
||||
# Please create a file "extra.conf" for persistent overrides to dovecot.conf
|
||||
# --------------------------------------------------------------------------
|
||||
# LDAP example:
|
||||
#passdb {
|
||||
# args = /etc/dovecot/ldap/passdb.conf
|
||||
# driver = ldap
|
||||
#}
|
||||
|
||||
auth_mechanisms = plain login
|
||||
#mail_debug = yes
|
||||
#auth_debug = yes
|
||||
#log_debug = category=fts-flatcurve # Activate Logging for Flatcurve FTS Searchings
|
||||
log_path = syslog
|
||||
disable_plaintext_auth = yes
|
||||
# Uncomment on NFS share
|
||||
#mmap_disable = yes
|
||||
#mail_fsync = always
|
||||
#mail_nfs_index = yes
|
||||
#mail_nfs_storage = yes
|
||||
login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k"
|
||||
mail_home = /var/vmail/%d/%n
|
||||
mail_location = maildir:~/
|
||||
mail_plugins = </etc/dovecot/mail_plugins
|
||||
mail_attachment_fs = crypt:set_prefix=mail_crypt_global:posix:
|
||||
mail_attachment_dir = /var/attachments
|
||||
mail_attachment_min_size = 128k
|
||||
# Significantly speeds up very large mailboxes, but is only safe to enable if
|
||||
# you do not manually modify the files in the `cur` directories in
|
||||
# mailcowdockerized_vmail-vol-1.
|
||||
# https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-performance/
|
||||
maildir_very_dirty_syncs = yes
|
||||
|
||||
# Dovecot 2.2
|
||||
#ssl_protocols = !SSLv3
|
||||
# Dovecot 2.3
|
||||
ssl_min_protocol = TLSv1.2
|
||||
|
||||
ssl_prefer_server_ciphers = yes
|
||||
ssl_cipher_list = ALL:!ADH:!LOW:!SSLv2:!SSLv3:!EXP:!aNULL:!eNULL:!3DES:!MD5:!PSK:!DSS:!RC4:!SEED:!IDEA:+HIGH:+MEDIUM
|
||||
|
||||
# Default in Dovecot 2.3
|
||||
ssl_options = no_compression no_ticket
|
||||
|
||||
# New in Dovecot 2.3
|
||||
ssl_dh = </etc/ssl/mail/dhparams.pem
|
||||
# Dovecot 2.2
|
||||
#ssl_dh_parameters_length = 2048
|
||||
log_timestamp = "%Y-%m-%d %H:%M:%S "
|
||||
recipient_delimiter = +
|
||||
auth_master_user_separator = *
|
||||
mail_shared_explicit_inbox = yes
|
||||
mail_prefetch_count = 30
|
||||
passdb {
|
||||
driver = lua
|
||||
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
|
||||
result_success = return-ok
|
||||
result_failure = continue
|
||||
result_internalfail = continue
|
||||
}
|
||||
# try a master passwd
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = /etc/dovecot/dovecot-master.passwd
|
||||
master = yes
|
||||
skip = authenticated
|
||||
}
|
||||
# check for regular password - if empty (e.g. force-passwd-reset), previous pass=yes passdbs also fail
|
||||
# a return of the following passdb is mandatory
|
||||
passdb {
|
||||
driver = lua
|
||||
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
|
||||
}
|
||||
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
|
||||
service doveadm {
|
||||
inet_listener {
|
||||
port = 12345
|
||||
}
|
||||
vsz_limit=2048 MB
|
||||
}
|
||||
!include /etc/dovecot/dovecot.folders.conf
|
||||
protocols = imap sieve lmtp pop3
|
||||
service dict {
|
||||
unix_listener dict {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
group = vmail
|
||||
}
|
||||
}
|
||||
service log {
|
||||
user = dovenull
|
||||
}
|
||||
service config {
|
||||
unix_listener config {
|
||||
user = root
|
||||
group = vmail
|
||||
mode = 0660
|
||||
}
|
||||
}
|
||||
service auth {
|
||||
inet_listener auth-inet {
|
||||
port = 10001
|
||||
}
|
||||
unix_listener auth-master {
|
||||
mode = 0600
|
||||
user = vmail
|
||||
}
|
||||
unix_listener auth-userdb {
|
||||
mode = 0600
|
||||
user = vmail
|
||||
}
|
||||
vsz_limit = 2G
|
||||
}
|
||||
service managesieve-login {
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
}
|
||||
inet_listener sieve_haproxy {
|
||||
port = 14190
|
||||
haproxy = yes
|
||||
}
|
||||
service_count = 1
|
||||
process_min_avail = 2
|
||||
vsz_limit = 1G
|
||||
}
|
||||
service imap-login {
|
||||
service_count = 1
|
||||
process_limit = 10000
|
||||
vsz_limit = 1G
|
||||
user = dovenull
|
||||
inet_listener imap_haproxy {
|
||||
port = 10143
|
||||
haproxy = yes
|
||||
}
|
||||
inet_listener imaps_haproxy {
|
||||
port = 10993
|
||||
ssl = yes
|
||||
haproxy = yes
|
||||
}
|
||||
}
|
||||
service pop3-login {
|
||||
service_count = 1
|
||||
vsz_limit = 1G
|
||||
inet_listener pop3_haproxy {
|
||||
port = 10110
|
||||
haproxy = yes
|
||||
}
|
||||
inet_listener pop3s_haproxy {
|
||||
port = 10995
|
||||
ssl = yes
|
||||
haproxy = yes
|
||||
}
|
||||
}
|
||||
service imap {
|
||||
executable = imap
|
||||
user = vmail
|
||||
vsz_limit = 1G
|
||||
}
|
||||
service managesieve {
|
||||
process_limit = 256
|
||||
}
|
||||
service lmtp {
|
||||
inet_listener lmtp-inet {
|
||||
port = 24
|
||||
}
|
||||
user = vmail
|
||||
}
|
||||
listen = *,[::]
|
||||
ssl_cert = </etc/ssl/mail/cert.pem
|
||||
ssl_key = </etc/ssl/mail/key.pem
|
||||
userdb {
|
||||
driver = passwd-file
|
||||
args = /etc/dovecot/dovecot-master.userdb
|
||||
}
|
||||
userdb {
|
||||
args = /etc/dovecot/sql/dovecot-dict-sql-userdb.conf
|
||||
driver = sql
|
||||
skip = found
|
||||
}
|
||||
protocol imap {
|
||||
mail_plugins = </etc/dovecot/mail_plugins_imap
|
||||
imap_metadata = yes
|
||||
}
|
||||
mail_attribute_dict = file:%h/dovecot-attributes
|
||||
protocol lmtp {
|
||||
mail_plugins = </etc/dovecot/mail_plugins_lmtp
|
||||
auth_socket_path = /var/run/dovecot/auth-master
|
||||
}
|
||||
protocol sieve {
|
||||
managesieve_logout_format = bytes=%i/%o
|
||||
}
|
||||
plugin {
|
||||
# Allow "any" or "authenticated" to be used in ACLs
|
||||
acl_anyone = </etc/dovecot/acl_anyone
|
||||
acl_shared_dict = file:/var/vmail/shared-mailboxes.db
|
||||
acl = vfile
|
||||
acl_user = %u
|
||||
quota = dict:Userquota::proxy::sqlquota
|
||||
quota_rule2 = Trash:storage=+100%%
|
||||
sieve = /var/vmail/sieve/%u.sieve
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve_vacation_send_from_recipient = yes
|
||||
sieve_redirect_envelope_from = recipient
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:/usr/lib/dovecot/sieve/report-spam.sieve
|
||||
# END
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:/usr/lib/dovecot/sieve/report-ham.sieve
|
||||
# END
|
||||
master_user = %u
|
||||
quota_warning = storage=95%% quota-warning 95 %u
|
||||
quota_warning2 = storage=80%% quota-warning 80 %u
|
||||
sieve_pipe_bin_dir = /usr/lib/dovecot/sieve
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
|
||||
sieve_extensions = +notify +imapflags +vacation-seconds +editheader
|
||||
sieve_max_script_size = 1M
|
||||
sieve_max_redirects = 100
|
||||
sieve_max_actions = 101
|
||||
sieve_quota_max_scripts = 0
|
||||
sieve_quota_max_storage = 0
|
||||
listescape_char = "\\"
|
||||
sieve_vacation_min_period = 5s
|
||||
sieve_vacation_max_period = 0
|
||||
sieve_vacation_default_period = 60s
|
||||
sieve_before = /var/vmail/sieve/global_sieve_before.sieve
|
||||
sieve_before2 = dict:proxy::sieve_before;name=active;bindir=/var/vmail/sieve_before_bindir
|
||||
sieve_after = dict:proxy::sieve_after;name=active;bindir=/var/vmail/sieve_after_bindir
|
||||
sieve_after2 = /var/vmail/sieve/global_sieve_after.sieve
|
||||
sieve_duplicate_default_period = 1m
|
||||
sieve_duplicate_max_period = 7d
|
||||
|
||||
# -- Global keys
|
||||
mail_crypt_global_private_key = </mail_crypt/ecprivkey.pem
|
||||
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
|
||||
mail_crypt_save_version = 2
|
||||
|
||||
# Enable compression while saving, lz4 Dovecot v2.2.11+
|
||||
zlib_save = lz4
|
||||
|
||||
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
|
||||
mail_log_fields = uid box msgid size
|
||||
mail_log_cached_only = yes
|
||||
|
||||
# Try set mail_replica
|
||||
!include_try /etc/dovecot/mail_replica.conf
|
||||
}
|
||||
service quota-warning {
|
||||
executable = script /usr/local/bin/quota_notify.py
|
||||
# use some unprivileged user for executing the quota warnings
|
||||
user = vmail
|
||||
unix_listener quota-warning {
|
||||
user = vmail
|
||||
}
|
||||
}
|
||||
dict {
|
||||
sqlquota = mysql:/etc/dovecot/sql/dovecot-dict-sql-quota.conf
|
||||
sieve_after = mysql:/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
|
||||
sieve_before = mysql:/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
|
||||
}
|
||||
remote 127.0.0.1 {
|
||||
disable_plaintext_auth = no
|
||||
}
|
||||
submission_host = postfix:588
|
||||
mail_max_userip_connections = 500
|
||||
service stats {
|
||||
unix_listener stats-writer {
|
||||
mode = 0660
|
||||
user = vmail
|
||||
}
|
||||
}
|
||||
imap_max_line_length = 2 M
|
||||
#auth_cache_verify_password_with_worker = yes
|
||||
#auth_cache_negative_ttl = 0
|
||||
#auth_cache_ttl = 30 s
|
||||
#auth_cache_size = 2 M
|
||||
service replicator {
|
||||
process_min_avail = 1
|
||||
}
|
||||
service aggregator {
|
||||
fifo_listener replication-notify-fifo {
|
||||
user = vmail
|
||||
}
|
||||
unix_listener replication-notify {
|
||||
user = vmail
|
||||
}
|
||||
}
|
||||
service replicator {
|
||||
unix_listener replicator-doveadm {
|
||||
mode = 0666
|
||||
}
|
||||
}
|
||||
replication_max_conns = 10
|
||||
doveadm_port = 12345
|
||||
replication_dsync_parameters = -d -l 30 -U -n INBOX
|
||||
# <Includes>
|
||||
!include_try /etc/dovecot/sni.conf
|
||||
!include_try /etc/dovecot/sogo_trusted_ip.conf
|
||||
!include_try /etc/dovecot/extra.conf
|
||||
!include_try /etc/dovecot/sogo-sso.conf
|
||||
!include_try /etc/dovecot/shared_namespace.conf
|
||||
!include_try /etc/dovecot/conf.d/fts.conf
|
||||
# </Includes>
|
||||
default_client_limit = 10400
|
||||
default_vsz_limit = 1024 M
|
||||
308
data/conf/dovecot/dovecot.folders.conf
Executable file
308
data/conf/dovecot/dovecot.folders.conf
Executable file
|
|
@ -0,0 +1,308 @@
|
|||
namespace inbox {
|
||||
inbox = yes
|
||||
location =
|
||||
separator = /
|
||||
mailbox "Trash" {
|
||||
auto = subscribe
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Deleted Messages" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Deleted Items" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Rubbish" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Gelöschte Objekte" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Gelöschte Elemente" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Papierkorb" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Itens Excluidos" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Itens Excluídos" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Lixeira" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Prullenbak" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Odstránené položky" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Koš" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Verwijderde items" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Удаленные" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Удаленные элементы" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Корзина" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Видалені" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Видалені елементи" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Кошик" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "废件箱" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "已删除消息" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "已删除邮件" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Archive" {
|
||||
auto = subscribe
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Archiv" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Archives" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Arquivo" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Arquivos" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Archief" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Archív" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Archivovať" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "归档" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Архив" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Архів" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Sent" {
|
||||
auto = subscribe
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Sent Messages" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Sent Items" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "已发送" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "已发送消息" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "已发送邮件" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Отправленные" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Отправленные элементы" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Надіслані" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Надіслані елементи" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Gesendet" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Gesendete Objekte" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Gesendete Elemente" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Itens Enviados" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Enviados" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Verzonden items" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Verzonden" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Odoslaná pošta" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Odoslané" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Drafts" {
|
||||
auto = subscribe
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Entwürfe" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Rascunhos" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Concepten" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Koncepty" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "草稿" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "草稿箱" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Черновики" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Чернетки" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Junk" {
|
||||
auto = subscribe
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Junk-E-Mail" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Junk E-Mail" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Spam" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Lixo Eletrônico" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Nevyžiadaná pošta" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Infikované položky" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Ongewenste e-mail" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "垃圾" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "垃圾箱" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Нежелательная почта" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Спам" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Небажана пошта" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Koncepty" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Nevyžádaná pošta" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Odstraněná pošta" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Odeslaná pošta" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Skräp" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Borttagna Meddelanden" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Arkiv" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Arkeverat" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Skickat" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Skickade Meddelanden" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Utkast" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Skraldespand" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Slettet mails" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Arkiv" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Arkiveret mails" {
|
||||
special_use = \Archive
|
||||
}
|
||||
mailbox "Sendt" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Sendte mails" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Udkast" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Kladde" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Πρόχειρα" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Απεσταλμένα" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Κάδος απορριμάτων" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Ανεπιθύμητα" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Αρχειοθετημένα" {
|
||||
special_use = \Archive
|
||||
}
|
||||
prefix =
|
||||
}
|
||||
9
data/conf/dovecot/ldap/passdb.conf
Executable file
9
data/conf/dovecot/ldap/passdb.conf
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#hosts = 1.2.3.4
|
||||
#dn = cn=admin,dc=example,dc=local
|
||||
#dnpass = password
|
||||
#ldap_version = 3
|
||||
#base = ou=People,dc=example,dc=local
|
||||
#auth_bind = no
|
||||
#pass_filter = (&(objectClass=posixAccount)(mail=%u))
|
||||
#pass_attrs = mail=user,userPassword=password
|
||||
#default_pass_scheme = SSHA
|
||||
35
data/conf/mysql/my.cnf
Executable file
35
data/conf/mysql/my.cnf
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
[mysqld]
|
||||
character-set-client-handshake = FALSE
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
#innodb_file_per_table = TRUE
|
||||
#innodb_file_format = barracuda
|
||||
#innodb_large_prefix = TRUE
|
||||
#sql_mode=IGNORE_SPACE,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
|
||||
max_allowed_packet = 192M
|
||||
max-connections = 550
|
||||
key_buffer_size = 0
|
||||
read_buffer_size = 192K
|
||||
sort_buffer_size = 2M
|
||||
innodb_buffer_pool_size = 24M
|
||||
read_rnd_buffer_size = 256K
|
||||
tmp_table_size = 24M
|
||||
performance_schema = 0
|
||||
innodb-strict-mode = 0
|
||||
thread_cache_size = 8
|
||||
query_cache_type = 0
|
||||
query_cache_size = 0
|
||||
max_heap_table_size = 48M
|
||||
thread_stack = 128K
|
||||
skip-host-cache
|
||||
skip-name-resolve
|
||||
log-warnings = 0
|
||||
event_scheduler = 1
|
||||
interactive_timeout = 3610
|
||||
wait_timeout = 3610
|
||||
|
||||
[client]
|
||||
default-character-set = utf8mb4
|
||||
|
||||
[mysql]
|
||||
default-character-set = utf8mb4
|
||||
242
data/conf/nginx/includes/site-defaults.conf
Executable file
242
data/conf/nginx/includes/site-defaults.conf
Executable file
|
|
@ -0,0 +1,242 @@
|
|||
|
||||
include /etc/nginx/mime.types;
|
||||
charset utf-8;
|
||||
override_charset on;
|
||||
|
||||
server_tokens off;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
|
||||
ssl_ecdh_curve X25519:X448:secp384r1:secp256k1;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_tickets off;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=15768000;";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
add_header X-Download-Options noopen;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Permitted-Cross-Domain-Policies none;
|
||||
add_header Referrer-Policy strict-origin;
|
||||
|
||||
index index.php index.html;
|
||||
|
||||
client_max_body_size 0;
|
||||
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied off;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
|
||||
|
||||
location ~ ^/(fonts|js|css|img)/ {
|
||||
expires max;
|
||||
add_header Cache-Control public;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
absolute_redirect off;
|
||||
root /web;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ @strip-ext;
|
||||
}
|
||||
|
||||
location /qhandler {
|
||||
rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2;
|
||||
}
|
||||
|
||||
location /edit {
|
||||
rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2;
|
||||
}
|
||||
|
||||
location @strip-ext {
|
||||
rewrite ^(.*)$ $1.php last;
|
||||
}
|
||||
|
||||
location ~ ^/api/v1/(.*)$ {
|
||||
try_files $uri $uri/ /json_api.php?query=$1&$args;
|
||||
}
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
default_type "text/plain";
|
||||
}
|
||||
|
||||
# If behind reverse proxy, forwards the correct IP
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
set_real_ip_from 172.16.0.0/12;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
set_real_ip_from fc00::/7;
|
||||
real_ip_header X-Forwarded-For;
|
||||
real_ip_recursive on;
|
||||
|
||||
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
|
||||
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
|
||||
|
||||
location ^~ /principals {
|
||||
return 301 /SOGo/dav;
|
||||
}
|
||||
|
||||
location ^~ /inc/lib/ {
|
||||
deny all;
|
||||
return 403;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass phpfpm:9002;
|
||||
fastcgi_index index.php;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_read_timeout 3600;
|
||||
fastcgi_send_timeout 3600;
|
||||
}
|
||||
|
||||
location /rspamd/ {
|
||||
location /rspamd/auth {
|
||||
# proxy_pass is not inherited
|
||||
proxy_pass http://rspamd:11334/auth;
|
||||
proxy_intercept_errors on;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_redirect off;
|
||||
error_page 401 /_rspamderror.php;
|
||||
}
|
||||
proxy_pass http://rspamd:11334/;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_redirect off;
|
||||
}
|
||||
|
||||
location ~* ^/Autodiscover/Autodiscover.xml {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass phpfpm:9002;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
try_files /autodiscover.php =404;
|
||||
}
|
||||
|
||||
location ~* ^/Autodiscover/Autodiscover.json {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass phpfpm:9002;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
try_files /autodiscover-json.php =404;
|
||||
}
|
||||
|
||||
location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass phpfpm:9002;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
try_files /autoconfig.php =404;
|
||||
}
|
||||
|
||||
location /sogo-auth-verify {
|
||||
internal;
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_pass http://127.0.0.1:65510/sogo-auth;
|
||||
proxy_pass_request_body off;
|
||||
}
|
||||
|
||||
location ^~ /Microsoft-Server-ActiveSync {
|
||||
include /etc/nginx/conf.d/includes/sogo_proxy_auth.conf;
|
||||
include /etc/nginx/conf.d/sogo_eas.active;
|
||||
proxy_connect_timeout 75;
|
||||
proxy_send_timeout 3600;
|
||||
proxy_read_timeout 3600;
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 64 512k;
|
||||
proxy_busy_buffers_size 512k;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
client_body_buffer_size 512k;
|
||||
client_max_body_size 0;
|
||||
}
|
||||
|
||||
location ^~ /SOGo {
|
||||
location ~* ^/SOGo/so/.*\.(xml|js|html|xhtml)$ {
|
||||
include /etc/nginx/conf.d/includes/sogo_proxy_auth.conf;
|
||||
include /etc/nginx/conf.d/sogo.active;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
|
||||
proxy_set_header x-webobjects-remote-host $remote_addr;
|
||||
proxy_set_header x-webobjects-server-name $server_name;
|
||||
proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host;
|
||||
proxy_set_header x-webobjects-server-port $server_port;
|
||||
proxy_hide_header Content-Type;
|
||||
add_header Content-Type text/plain;
|
||||
break;
|
||||
}
|
||||
include /etc/nginx/conf.d/includes/sogo_proxy_auth.conf;
|
||||
include /etc/nginx/conf.d/sogo.active;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
|
||||
proxy_set_header x-webobjects-remote-host $remote_addr;
|
||||
proxy_set_header x-webobjects-server-name $server_name;
|
||||
proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host;
|
||||
proxy_set_header x-webobjects-server-port $server_port;
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 64 512k;
|
||||
proxy_busy_buffers_size 512k;
|
||||
proxy_send_timeout 3600;
|
||||
proxy_read_timeout 3600;
|
||||
client_body_buffer_size 128k;
|
||||
client_max_body_size 0;
|
||||
break;
|
||||
}
|
||||
|
||||
location ~* /sogo$ {
|
||||
return 301 $client_req_scheme://$http_host/SOGo;
|
||||
}
|
||||
|
||||
location /SOGo.woa/WebServerResources/ {
|
||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
||||
}
|
||||
|
||||
location /.woa/WebServerResources/ {
|
||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
||||
}
|
||||
|
||||
location /SOGo/WebServerResources/ {
|
||||
alias /usr/lib/GNUstep/SOGo/WebServerResources/;
|
||||
}
|
||||
|
||||
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) {
|
||||
alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
|
||||
}
|
||||
|
||||
include /etc/nginx/conf.d/site.*.custom;
|
||||
|
||||
error_page 502 @awaitingupstream;
|
||||
|
||||
location @awaitingupstream {
|
||||
rewrite ^(.*)$ /_status.502.html break;
|
||||
}
|
||||
|
||||
location ~ ^/cache/(.*)$ {
|
||||
try_files $uri $uri/ /resource.php?file=$1;
|
||||
}
|
||||
8
data/conf/nginx/includes/sogo_proxy_auth.conf
Executable file
8
data/conf/nginx/includes/sogo_proxy_auth.conf
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
auth_request /sogo-auth-verify;
|
||||
auth_request_set $user $upstream_http_x_user;
|
||||
auth_request_set $auth $upstream_http_x_auth;
|
||||
auth_request_set $auth_type $upstream_http_x_auth_type;
|
||||
proxy_set_header x-webobjects-remote-user "$user";
|
||||
proxy_set_header Authorization "$auth";
|
||||
proxy_set_header x-webobjects-auth-type "$auth_type";
|
||||
|
||||
2
data/conf/nginx/templates/listen_plain.template
Executable file
2
data/conf/nginx/templates/listen_plain.template
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
listen ${HTTP_PORT};
|
||||
listen [::]:${HTTP_PORT};
|
||||
3
data/conf/nginx/templates/listen_ssl.template
Executable file
3
data/conf/nginx/templates/listen_ssl.template
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
listen ${HTTPS_PORT} ssl;
|
||||
listen [::]:${HTTPS_PORT} ssl;
|
||||
http2 on;
|
||||
1
data/conf/nginx/templates/server_name.template.sh
Executable file
1
data/conf/nginx/templates/server_name.template.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
echo "server_name ${MAILCOW_HOSTNAME} autodiscover.* autoconfig.* $(echo ${ADDITIONAL_SERVER_NAMES} | tr ',' ' ');"
|
||||
38
data/conf/nginx/templates/sites.template.sh
Executable file
38
data/conf/nginx/templates/sites.template.sh
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
echo '
|
||||
server {
|
||||
listen 127.0.0.1:65510;
|
||||
include /etc/nginx/conf.d/listen_plain.active;
|
||||
include /etc/nginx/conf.d/listen_ssl.active;
|
||||
|
||||
ssl_certificate /etc/ssl/mail/cert.pem;
|
||||
ssl_certificate_key /etc/ssl/mail/key.pem;
|
||||
|
||||
include /etc/nginx/conf.d/server_name.active;
|
||||
|
||||
include /etc/nginx/conf.d/includes/site-defaults.conf;
|
||||
}
|
||||
';
|
||||
for cert_dir in /etc/ssl/mail/*/ ; do
|
||||
if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then
|
||||
continue
|
||||
fi
|
||||
# do not create vhost for default-certificate. the cert is already in the default server listen
|
||||
domains="$(cat ${cert_dir}domains | sed -e 's/^[[:space:]]*//')"
|
||||
case "${domains}" in
|
||||
"") continue;;
|
||||
"${MAILCOW_HOSTNAME}"*) continue;;
|
||||
esac
|
||||
echo -n '
|
||||
server {
|
||||
include /etc/nginx/conf.d/listen_ssl.active;
|
||||
|
||||
ssl_certificate '${cert_dir}'cert.pem;
|
||||
ssl_certificate_key '${cert_dir}'key.pem;
|
||||
';
|
||||
echo -n '
|
||||
server_name '${domains}';
|
||||
|
||||
include /etc/nginx/conf.d/includes/site-defaults.conf;
|
||||
}
|
||||
';
|
||||
done
|
||||
1
data/conf/nginx/templates/sogo.template
Executable file
1
data/conf/nginx/templates/sogo.template
Executable file
|
|
@ -0,0 +1 @@
|
|||
proxy_pass http://${IPV4_NETWORK}.248:20000;
|
||||
5
data/conf/nginx/templates/sogo_eas.template.sh
Executable file
5
data/conf/nginx/templates/sogo_eas.template.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
if printf "%s\n" "${SKIP_SOGO}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then
|
||||
echo "return 410;"
|
||||
else
|
||||
echo "proxy_pass http://${IPV4_NETWORK}.248:20000/SOGo/Microsoft-Server-ActiveSync;"
|
||||
fi
|
||||
7
data/conf/phpfpm/php-conf.d/opcache-recommended.ini
Executable file
7
data/conf/phpfpm/php-conf.d/opcache-recommended.ini
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
opcache.enable=1
|
||||
opcache.enable_cli=1
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.memory_consumption=128
|
||||
opcache.save_comments=1
|
||||
opcache.revalidate_freq=1
|
||||
3
data/conf/phpfpm/php-conf.d/other.ini
Executable file
3
data/conf/phpfpm/php-conf.d/other.ini
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
max_execution_time = 3600
|
||||
max_input_time = 3600
|
||||
memory_limit = 512M
|
||||
3
data/conf/phpfpm/php-conf.d/upload.ini
Executable file
3
data/conf/phpfpm/php-conf.d/upload.ini
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
file_uploads = On
|
||||
upload_max_filesize = 64M
|
||||
post_max_size = 64M
|
||||
29
data/conf/phpfpm/php-fpm.d/pools.conf
Executable file
29
data/conf/phpfpm/php-fpm.d/pools.conf
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
[system-worker]
|
||||
user = www-data
|
||||
group = www-data
|
||||
pm = dynamic
|
||||
pm.max_children = 15
|
||||
pm.start_servers = 2
|
||||
pm.min_spare_servers = 2
|
||||
pm.max_spare_servers = 4
|
||||
listen = [::]:9001
|
||||
access.log = /proc/self/fd/2
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
php_admin_value[memory_limit] = 256M
|
||||
php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink
|
||||
|
||||
[web-worker]
|
||||
user = www-data
|
||||
group = www-data
|
||||
pm = dynamic
|
||||
pm.max_children = 50
|
||||
pm.start_servers = 10
|
||||
pm.min_spare_servers = 10
|
||||
pm.max_spare_servers = 15
|
||||
listen = [::]:9002
|
||||
access.log = /proc/self/fd/2
|
||||
clear_env = no
|
||||
catch_workers_output = yes
|
||||
php_admin_value[memory_limit] = 512M
|
||||
php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink
|
||||
0
data/conf/phpfpm/sogo-sso/.gitkeep
Executable file
0
data/conf/phpfpm/sogo-sso/.gitkeep
Executable file
20
data/conf/postfix/anonymize_headers.pcre
Executable file
20
data/conf/postfix/anonymize_headers.pcre
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
if /^\s*Received:.*Authenticated sender.*\(Postcow\)/
|
||||
#/^Received: from .*? \([\w-.]* \[.*?\]\)\s+\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (E?SMTPS?A?) id ([A-F0-9]+).+;.*?/
|
||||
/^Received: from .*? \([\w\-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (.*)/
|
||||
REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with $3
|
||||
endif
|
||||
if /^\s*Received: from.* \(.*dovecot-mailcow.*mailcow-network.*\).*\(Postcow\)/
|
||||
/^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postcow\) with (.*)/
|
||||
REPLACE Received: from sieve (sieve $3) by $4 (Postcow) with $5
|
||||
endif
|
||||
if /^\s*Received: from.* \(.*rspamd-mailcow.*mailcow-network.*\).*\(Postcow\)/
|
||||
/^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postcow\) with (.*)/
|
||||
REPLACE Received: from rspamd (rspamd $3) by $4 (Postcow) with $5
|
||||
endif
|
||||
/^\s*X-Enigmail/ IGNORE
|
||||
# Not removing Mailer by default, might be signed
|
||||
#/^\s*X-Mailer/ IGNORE
|
||||
/^\s*X-Originating-IP/ IGNORE
|
||||
/^\s*X-Forward/ IGNORE
|
||||
# Not removing UA by default, might be signed
|
||||
#/^\s*User-Agent/ IGNORE
|
||||
2
data/conf/postfix/local_transport
Executable file
2
data/conf/postfix/local_transport
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
/watchdog@localhost$/ watchdog_discard:
|
||||
/localhost$/ local:
|
||||
202
data/conf/postfix/main.cf
Executable file
202
data/conf/postfix/main.cf
Executable file
|
|
@ -0,0 +1,202 @@
|
|||
# --------------------------------------------------------------------------
|
||||
# Please create a file "extra.cf" for persistent overrides to main.cf
|
||||
# --------------------------------------------------------------------------
|
||||
biff = no
|
||||
append_dot_mydomain = no
|
||||
smtpd_tls_cert_file = /etc/ssl/mail/cert.pem
|
||||
smtpd_tls_key_file = /etc/ssl/mail/key.pem
|
||||
tls_server_sni_maps = hash:/opt/postfix/conf/sni.map
|
||||
smtpd_tls_received_header = yes
|
||||
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
|
||||
smtpd_relay_restrictions = permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
defer_unauth_destination
|
||||
smtpd_forbid_bare_newline = yes
|
||||
# alias maps are auto-generated in postfix.sh on startup
|
||||
alias_maps = hash:/etc/aliases
|
||||
alias_database = hash:/etc/aliases
|
||||
relayhost =
|
||||
mynetworks_style = subnet
|
||||
mailbox_size_limit = 0
|
||||
recipient_delimiter = +
|
||||
inet_interfaces = all
|
||||
inet_protocols = all
|
||||
bounce_queue_lifetime = 1d
|
||||
broken_sasl_auth_clients = yes
|
||||
disable_vrfy_command = yes
|
||||
maximal_backoff_time = 1800s
|
||||
maximal_queue_lifetime = 5d
|
||||
delay_warning_time = 4h
|
||||
message_size_limit = 104857600
|
||||
milter_default_action = tempfail
|
||||
milter_protocol = 6
|
||||
minimal_backoff_time = 300s
|
||||
plaintext_reject_code = 550
|
||||
postscreen_access_list = permit_mynetworks,
|
||||
cidr:/opt/postfix/conf/custom_postscreen_whitelist.cidr,
|
||||
cidr:/opt/postfix/conf/postscreen_access.cidr,
|
||||
tcp:127.0.0.1:10027
|
||||
postscreen_bare_newline_enable = no
|
||||
postscreen_blacklist_action = drop
|
||||
postscreen_cache_cleanup_interval = 24h
|
||||
postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache
|
||||
postscreen_dnsbl_action = enforce
|
||||
postscreen_dnsbl_threshold = 6
|
||||
postscreen_dnsbl_ttl = 5m
|
||||
postscreen_greet_action = enforce
|
||||
postscreen_greet_banner = $smtpd_banner
|
||||
postscreen_greet_ttl = 2d
|
||||
postscreen_greet_wait = 3s
|
||||
postscreen_non_smtp_command_enable = no
|
||||
postscreen_pipelining_enable = no
|
||||
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
|
||||
$sender_dependent_default_transport_maps,
|
||||
$smtp_tls_policy_maps,
|
||||
$local_recipient_maps,
|
||||
$mydestination,
|
||||
$virtual_alias_maps,
|
||||
$virtual_alias_domains,
|
||||
$virtual_mailbox_maps,
|
||||
$virtual_mailbox_domains,
|
||||
$relay_recipient_maps,
|
||||
$relay_domains,
|
||||
$canonical_maps,
|
||||
$sender_canonical_maps,
|
||||
$sender_bcc_maps,
|
||||
$recipient_bcc_maps,
|
||||
$recipient_canonical_maps,
|
||||
$relocated_maps,
|
||||
$transport_maps,
|
||||
$mynetworks,
|
||||
$smtpd_sender_login_maps,
|
||||
$smtp_sasl_password_maps
|
||||
queue_run_delay = 300s
|
||||
relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf
|
||||
relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
|
||||
sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf
|
||||
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
|
||||
smtp_tls_cert_file = /etc/ssl/mail/cert.pem
|
||||
smtp_tls_key_file = /etc/ssl/mail/key.pem
|
||||
smtp_tls_loglevel = 1
|
||||
smtp_dns_support_level = dnssec
|
||||
smtp_tls_security_level = dane
|
||||
smtpd_data_restrictions = reject_unauth_pipelining, permit
|
||||
smtpd_delay_reject = yes
|
||||
smtpd_error_sleep_time = 10s
|
||||
smtpd_forbid_bare_newline = yes
|
||||
smtpd_hard_error_limit = ${stress?1}${stress:5}
|
||||
smtpd_helo_required = yes
|
||||
smtpd_proxy_timeout = 600s
|
||||
smtpd_recipient_restrictions = check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
|
||||
permit_sasl_authenticated,
|
||||
permit_mynetworks,
|
||||
check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
|
||||
reject_invalid_helo_hostname,
|
||||
reject_unauth_destination
|
||||
smtpd_sasl_auth_enable = yes
|
||||
smtpd_sasl_authenticated_header = yes
|
||||
smtpd_sasl_path = inet:dovecot:10001
|
||||
smtpd_sasl_type = dovecot
|
||||
smtpd_sender_login_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf
|
||||
smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch,
|
||||
permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_unlisted_sender,
|
||||
reject_unknown_sender_domain
|
||||
smtpd_soft_error_limit = 3
|
||||
smtpd_tls_auth_only = yes
|
||||
smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem
|
||||
smtpd_tls_eecdh_grade = auto
|
||||
smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
|
||||
smtpd_tls_loglevel = 1
|
||||
|
||||
# Mandatory protocols and ciphers are used when a connections is enforced to use TLS
|
||||
# Does _not_ apply to enforced incoming TLS settings per mailbox
|
||||
smtp_tls_mandatory_protocols = >=TLSv1.2
|
||||
lmtp_tls_mandatory_protocols = >=TLSv1.2
|
||||
smtpd_tls_mandatory_protocols = >=TLSv1.2
|
||||
smtpd_tls_mandatory_ciphers = high
|
||||
|
||||
smtp_tls_protocols = >=TLSv1.2
|
||||
lmtp_tls_protocols = >=TLSv1.2
|
||||
smtpd_tls_protocols = >=TLSv1.2
|
||||
|
||||
smtpd_tls_security_level = may
|
||||
tls_preempt_cipherlist = yes
|
||||
tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION
|
||||
virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_resource_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf
|
||||
virtual_gid_maps = static:5000
|
||||
virtual_mailbox_base = /var/vmail/
|
||||
virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
|
||||
# -- moved to rspamd on 2021-06-01
|
||||
#recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf
|
||||
#sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf
|
||||
recipient_canonical_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
|
||||
recipient_canonical_classes = envelope_recipient
|
||||
virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf
|
||||
virtual_minimum_uid = 104
|
||||
virtual_transport = lmtp:inet:dovecot:24
|
||||
virtual_uid_maps = static:5000
|
||||
smtpd_milters = inet:rspamd:9900
|
||||
non_smtpd_milters = inet:rspamd:9900
|
||||
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
|
||||
mydestination = localhost.localdomain, localhost
|
||||
smtp_address_preference = any
|
||||
smtp_sender_dependent_authentication = yes
|
||||
smtp_sasl_auth_enable = yes
|
||||
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
||||
smtp_sasl_security_options =
|
||||
smtp_sasl_mechanism_filter = plain, login
|
||||
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
|
||||
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
||||
mail_name = Postcow
|
||||
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
||||
# Use custom_transport.pcre for custom transports
|
||||
transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
|
||||
pcre:/opt/postfix/conf/local_transport,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
|
||||
smtp_sasl_auth_soft_bounce = no
|
||||
postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking
|
||||
smtpd_discard_ehlo_keywords = chunking, silent-discard
|
||||
compatibility_level = 3.7
|
||||
smtputf8_enable = no
|
||||
# Define protocols for SMTPS and submission service
|
||||
submission_smtpd_tls_mandatory_protocols = >=TLSv1.2
|
||||
smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2
|
||||
parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients
|
||||
|
||||
# DO NOT EDIT ANYTHING BELOW #
|
||||
# Overrides #
|
||||
|
||||
postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
|
||||
hostkarma.junkemailfilter.com=127.0.0.1*-2
|
||||
list.dnswl.org=127.0.[0..255].0*-2
|
||||
list.dnswl.org=127.0.[0..255].1*-4
|
||||
list.dnswl.org=127.0.[0..255].2*-6
|
||||
list.dnswl.org=127.0.[0..255].3*-8
|
||||
ix.dnsbl.manitu.net*2
|
||||
bl.spamcop.net*2
|
||||
bl.suomispam.net*2
|
||||
hostkarma.junkemailfilter.com=127.0.0.2*3
|
||||
hostkarma.junkemailfilter.com=127.0.0.4*2
|
||||
hostkarma.junkemailfilter.com=127.0.1.2*1
|
||||
backscatter.spameatingmonkey.net*2
|
||||
bl.ipv6.spameatingmonkey.net*2
|
||||
bl.spameatingmonkey.net*2
|
||||
b.barracudacentral.org=127.0.0.2*7
|
||||
bl.mailspike.net=127.0.0.2*5
|
||||
bl.mailspike.net=127.0.0.[10;11;12]*4
|
||||
zen.spamhaus.org=127.0.0.[10;11]*8
|
||||
zen.spamhaus.org=127.0.0.[4..7]*6
|
||||
zen.spamhaus.org=127.0.0.3*4
|
||||
zen.spamhaus.org=127.0.0.2*3
|
||||
|
||||
# User Overrides
|
||||
myhostname = hermes.netgrimoire.com
|
||||
|
||||
146
data/conf/postfix/master.cf
Executable file
146
data/conf/postfix/master.cf
Executable file
|
|
@ -0,0 +1,146 @@
|
|||
# inter-mx with postscreen on 25/tcp
|
||||
smtp inet n - n - 1 postscreen
|
||||
10025 inet n - n - 1 postscreen
|
||||
-o postscreen_upstream_proxy_protocol=haproxy
|
||||
-o syslog_name=haproxy
|
||||
smtpd pass - - n - - smtpd
|
||||
-o smtpd_sasl_auth_enable=no
|
||||
-o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain
|
||||
|
||||
# smtpd tls-wrapped (smtps) on 465/tcp
|
||||
# TLS protocol can be modified by setting smtps_smtpd_tls_mandatory_protocols in extra.cf
|
||||
smtps inet n - n - - smtpd
|
||||
-o smtpd_tls_wrappermode=yes
|
||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||
-o smtpd_tls_mandatory_protocols=$smtps_smtpd_tls_mandatory_protocols
|
||||
-o tls_preempt_cipherlist=yes
|
||||
-o cleanup_service_name=smtp_sender_cleanup
|
||||
-o syslog_name=postfix/smtps
|
||||
10465 inet n - n - - smtpd
|
||||
-o smtpd_upstream_proxy_protocol=haproxy
|
||||
-o smtpd_tls_wrappermode=yes
|
||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||
-o smtpd_tls_mandatory_protocols=$smtps_smtpd_tls_mandatory_protocols
|
||||
-o tls_preempt_cipherlist=yes
|
||||
-o cleanup_service_name=smtp_sender_cleanup
|
||||
-o syslog_name=postfix/smtps-haproxy
|
||||
|
||||
# smtpd with starttls on 587/tcp
|
||||
# TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf
|
||||
submission inet n - n - - smtpd
|
||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||
-o smtpd_enforce_tls=yes
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
|
||||
-o tls_preempt_cipherlist=yes
|
||||
-o cleanup_service_name=smtp_sender_cleanup
|
||||
-o syslog_name=postfix/submission
|
||||
10587 inet n - n - - smtpd
|
||||
-o smtpd_upstream_proxy_protocol=haproxy
|
||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||
-o smtpd_enforce_tls=yes
|
||||
-o smtpd_tls_security_level=encrypt
|
||||
-o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols
|
||||
-o tls_preempt_cipherlist=yes
|
||||
-o cleanup_service_name=smtp_sender_cleanup
|
||||
-o syslog_name=postfix/submission-haproxy
|
||||
|
||||
# used by SOGo
|
||||
# smtpd_sender_restrictions should match main.cf, but with check_sasl_access prepended for login-as-mailbox-user function
|
||||
588 inet n - n - - smtpd
|
||||
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
|
||||
-o smtpd_tls_auth_only=no
|
||||
-o smtpd_sender_restrictions=check_sasl_access,regexp:/opt/postfix/conf/allow_mailcow_local.regexp,reject_authenticated_sender_login_mismatch,permit_mynetworks,permit_sasl_authenticated,reject_unlisted_sender,reject_unknown_sender_domain
|
||||
-o cleanup_service_name=smtp_sender_cleanup
|
||||
-o syslog_name=postfix/sogo
|
||||
|
||||
# used to reinject quarantine mails
|
||||
590 inet n - n - - smtpd
|
||||
-o smtpd_helo_restrictions=
|
||||
-o smtpd_client_restrictions=permit_mynetworks,reject
|
||||
-o smtpd_tls_auth_only=no
|
||||
-o smtpd_milters=
|
||||
-o non_smtpd_milters=
|
||||
-o syslog_name=postfix/quarantine
|
||||
|
||||
# used to send bcc mails
|
||||
591 inet n - n - - smtpd
|
||||
-o smtpd_helo_restrictions=
|
||||
-o smtpd_client_restrictions=permit_mynetworks,reject
|
||||
-o smtpd_tls_auth_only=no
|
||||
-o smtpd_milters=
|
||||
-o non_smtpd_milters=
|
||||
-o syslog_name=postfix/bcc
|
||||
|
||||
# enforced smtp connector
|
||||
smtp_enforced_tls unix - - n - - smtp
|
||||
-o smtp_tls_security_level=encrypt
|
||||
-o syslog_name=enforced-tls-smtp
|
||||
-o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter
|
||||
|
||||
# smtp connector used, when a transport map matched
|
||||
# this helps to have different sasl maps than we have with sender dependent transport maps
|
||||
smtp_via_transport_maps unix - - n - - smtp
|
||||
-o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf
|
||||
|
||||
tlsproxy unix - - n - 0 tlsproxy
|
||||
dnsblog unix - - n - 0 dnsblog
|
||||
pickup fifo n - n 60 1 pickup
|
||||
cleanup unix n - n - 0 cleanup
|
||||
qmgr fifo n - n 300 1 qmgr
|
||||
tlsmgr unix - - n 1000? 1 tlsmgr
|
||||
rewrite unix - - n - - trivial-rewrite
|
||||
bounce unix - - n - 0 bounce
|
||||
defer unix - - n - 0 bounce
|
||||
trace unix - - n - 0 bounce
|
||||
verify unix - - n - 1 verify
|
||||
flush unix n - n 1000? 0 flush
|
||||
proxymap unix - - n - - proxymap
|
||||
proxywrite unix - - n - 1 proxymap
|
||||
smtp unix - - n - - smtp
|
||||
relay unix - - n - - smtp
|
||||
showq unix n - n - - showq
|
||||
error unix - - n - - error
|
||||
retry unix - - n - - error
|
||||
discard unix - - n - - discard
|
||||
local unix - n n - - local
|
||||
virtual unix - n n - - virtual
|
||||
lmtp unix - - n - - lmtp
|
||||
anvil unix - - n - 1 anvil
|
||||
scache unix - - n - 1 scache
|
||||
maildrop unix - n n - - pipe flags=DRhu
|
||||
user=vmail argv=/usr/bin/maildrop -d ${recipient}
|
||||
|
||||
# used to anonymize sender IP
|
||||
smtp_sender_cleanup unix n - y - 0 cleanup
|
||||
-o header_checks=$smtp_header_checks
|
||||
|
||||
# start whitelist_fwd
|
||||
127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh
|
||||
# end whitelist_fwd
|
||||
|
||||
# start watchdog-specific
|
||||
# logs to local7 (hidden)
|
||||
589 inet n - n - - smtpd
|
||||
-o smtpd_client_restrictions=permit_mynetworks,reject
|
||||
-o syslog_name=watchdog
|
||||
-o syslog_facility=local7
|
||||
-o smtpd_milters=
|
||||
-o cleanup_service_name=watchdog_cleanup
|
||||
-o non_smtpd_milters=
|
||||
watchdog_cleanup unix n - n - 0 cleanup
|
||||
-o syslog_name=watchdog
|
||||
-o syslog_facility=local7
|
||||
-o queue_service_name=watchdog_qmgr
|
||||
watchdog_qmgr fifo n - n 300 1 qmgr
|
||||
-o syslog_facility=local7
|
||||
-o syslog_name=watchdog
|
||||
-o rewrite_service_name=watchdog_rewrite
|
||||
watchdog_rewrite unix - - n - - trivial-rewrite
|
||||
-o syslog_facility=local7
|
||||
-o syslog_name=watchdog
|
||||
-o local_transport=watchdog_discard
|
||||
watchdog_discard unix - - n - - discard
|
||||
-o syslog_facility=local7
|
||||
-o syslog_name=watchdog
|
||||
# end watchdog-specific
|
||||
1958
data/conf/postfix/postscreen_access.cidr
Executable file
1958
data/conf/postfix/postscreen_access.cidr
Executable file
File diff suppressed because it is too large
Load diff
6
data/conf/postfix/smtp_dsn_filter
Executable file
6
data/conf/postfix/smtp_dsn_filter
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
/^4(\.\d+\.\d+ TLS is required, but host \S+ refused to start TLS: .+)/
|
||||
5$1
|
||||
/^4(\.\d+\.\d+ TLS is required, but was not offered by host .+)/
|
||||
5$1
|
||||
/^4.7.5(.*)/
|
||||
5.7.5$1
|
||||
174
data/conf/rspamd/dynmaps/aliasexp.php
Executable file
174
data/conf/rspamd/dynmaps/aliasexp.php
Executable file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
// File size is limited by Nginx site to 10M
|
||||
// To speed things up, we do not include prerequisites
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init database
|
||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("ALIASEXP: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
|
||||
function parse_email($email) {
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||
$a = strrpos($email, '@');
|
||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
||||
}
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
if (!is_array($_SERVER)) {
|
||||
return array();
|
||||
}
|
||||
$headers = array();
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
||||
// Read headers
|
||||
$headers = getallheaders();
|
||||
// Get rcpt
|
||||
$rcpt = $headers['Rcpt'];
|
||||
// Remove tag
|
||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
||||
// Parse email address
|
||||
$parsed_rcpt = parse_email($rcpt);
|
||||
// Create array of final mailboxes
|
||||
$rcpt_final_mailboxes = array();
|
||||
|
||||
// Skip if not a mailcow handled domain
|
||||
try {
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
error_log("ALIASEXP: " . $e . PHP_EOL);
|
||||
http_response_code(504);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
|
||||
//
|
||||
// rcpt
|
||||
// |
|
||||
// mailbox <-- goto ---> alias1, alias2, mailbox2
|
||||
// | |
|
||||
// mailbox3 |
|
||||
// |
|
||||
// alias3 ---> mailbox4
|
||||
//
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':rcpt' => $rcpt
|
||||
));
|
||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
if (empty($gotos)) {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':rcpt' => '@' . $parsed_rcpt['domain']
|
||||
));
|
||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
}
|
||||
if (empty($gotos)) {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(':rcpt' => $parsed_rcpt['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
$gotos = $parsed_rcpt['local'] . '@' . $goto_branch;
|
||||
}
|
||||
}
|
||||
$gotos_array = explode(',', $gotos);
|
||||
|
||||
$loop_c = 0;
|
||||
|
||||
while (count($gotos_array) != 0 && $loop_c <= 20) {
|
||||
|
||||
// Loop through all found gotos
|
||||
foreach ($gotos_array as $index => &$goto) {
|
||||
error_log("ALIAS EXPANDER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
||||
$stmt->execute(array(':goto' => $goto));
|
||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
||||
if (!empty($username)) {
|
||||
error_log("ALIAS EXPANDER: http pipe: mailbox found: " . $username . PHP_EOL);
|
||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
||||
$rcpt_final_mailboxes[] = $username;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$parsed_goto = parse_email($goto);
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
||||
$stmt->execute(array(':goto' => $goto));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
if ($goto_branch) {
|
||||
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
error_log("ALIAS EXPANDER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// goto item was processed, unset
|
||||
unset($gotos_array[$index]);
|
||||
}
|
||||
|
||||
// Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
|
||||
if (!empty($goto_branch_array)) {
|
||||
$gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
|
||||
unset($goto_branch_array);
|
||||
}
|
||||
|
||||
// Reindex array
|
||||
$gotos_array = array_values($gotos_array);
|
||||
|
||||
// Force exit if loop cannot be solved
|
||||
// Postfix does not allow for alias loops, so this should never happen.
|
||||
$loop_c++;
|
||||
error_log("ALIAS EXPANDER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("ALIAS EXPANDER: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(502);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Does also return the mailbox name if question == answer (query == mailbox)
|
||||
if (count($rcpt_final_mailboxes) == 1) {
|
||||
error_log("ALIASEXP: direct alias " . $rcpt . " expanded to " . $rcpt_final_mailboxes[0] . PHP_EOL);
|
||||
echo trim($rcpt_final_mailboxes[0]);
|
||||
}
|
||||
88
data/conf/rspamd/dynmaps/bcc.php
Executable file
88
data/conf/rspamd/dynmaps/bcc.php
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
// File size is limited by Nginx site to 10M
|
||||
// To speed things up, we do not include prerequisites
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init database
|
||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
|
||||
function parse_email($email) {
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||
$a = strrpos($email, '@');
|
||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
||||
}
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
if (!is_array($_SERVER)) {
|
||||
return array();
|
||||
}
|
||||
$headers = array();
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
||||
// Read headers
|
||||
$headers = getallheaders();
|
||||
// Get rcpt
|
||||
$rcpt = $headers['Rcpt'];
|
||||
// Get from
|
||||
$from = $headers['From'];
|
||||
// Remove tags
|
||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
||||
$from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from);
|
||||
|
||||
try {
|
||||
if (!empty($rcpt)) {
|
||||
$stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':local_dest' => $rcpt
|
||||
));
|
||||
$bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest'];
|
||||
if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) {
|
||||
error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL);
|
||||
http_response_code(201);
|
||||
echo trim($bcc_dest);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
if (!empty($from)) {
|
||||
$stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':local_dest' => $from
|
||||
));
|
||||
$bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest'];
|
||||
if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) {
|
||||
error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL);
|
||||
http_response_code(201);
|
||||
echo trim($bcc_dest);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(502);
|
||||
exit;
|
||||
}
|
||||
|
||||
113
data/conf/rspamd/dynmaps/footer.php
Executable file
113
data/conf/rspamd/dynmaps/footer.php
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
// File size is limited by Nginx site to 10M
|
||||
// To speed things up, we do not include prerequisites
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init database
|
||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("FOOTER: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
if (!is_array($_SERVER)) {
|
||||
return array();
|
||||
}
|
||||
$headers = array();
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
||||
// Read headers
|
||||
$headers = getallheaders();
|
||||
// Get Domain
|
||||
$domain = $headers['Domain'];
|
||||
// Get Username
|
||||
$username = $headers['Username'];
|
||||
// Get From
|
||||
$from = $headers['From'];
|
||||
// define empty footer
|
||||
$empty_footer = json_encode(array(
|
||||
'html' => '',
|
||||
'plain' => '',
|
||||
'skip_replies' => 0,
|
||||
'vars' => array()
|
||||
));
|
||||
|
||||
error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL);
|
||||
|
||||
try {
|
||||
// try get $target_domain if $domain is an alias_domain
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
|
||||
WHERE `alias_domain` = :alias_domain");
|
||||
$stmt->execute(array(
|
||||
':alias_domain' => $domain
|
||||
));
|
||||
$alias_domain = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$alias_domain) {
|
||||
$target_domain = $domain;
|
||||
} else {
|
||||
$target_domain = $alias_domain['target_domain'];
|
||||
}
|
||||
|
||||
// get footer associated with the domain
|
||||
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer`
|
||||
WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $target_domain
|
||||
));
|
||||
$footer = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// check if the sender is excluded
|
||||
if (in_array($from, json_decode($footer['mbox_exclude']))){
|
||||
$footer = false;
|
||||
}
|
||||
if (in_array($domain, json_decode($footer['alias_domain_exclude']))){
|
||||
$footer = false;
|
||||
}
|
||||
if (empty($footer)){
|
||||
echo $empty_footer;
|
||||
exit;
|
||||
}
|
||||
error_log("FOOTER: " . json_encode($footer) . PHP_EOL);
|
||||
|
||||
// footer will be applied
|
||||
// get custom mailbox attributes to insert into the footer
|
||||
$stmt = $pdo->prepare("SELECT `custom_attributes` FROM `mailbox` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
));
|
||||
$custom_attributes = $stmt->fetch(PDO::FETCH_ASSOC)['custom_attributes'];
|
||||
if (empty($custom_attributes)){
|
||||
$custom_attributes = (object)array();
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
error_log("FOOTER: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(502);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// return footer
|
||||
$footer["vars"] = $custom_attributes;
|
||||
echo json_encode($footer);
|
||||
57
data/conf/rspamd/dynmaps/forwardinghosts.php
Executable file
57
data/conf/rspamd/dynmaps/forwardinghosts.php
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
header('Content-Type: text/plain');
|
||||
ini_set('error_reporting', 0);
|
||||
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
|
||||
function in_net($addr, $net) {
|
||||
$net = explode('/', $net);
|
||||
if (count($net) > 1) {
|
||||
$mask = $net[1];
|
||||
}
|
||||
$net = inet_pton($net[0]);
|
||||
$addr = inet_pton($addr);
|
||||
$length = strlen($net); // 4 for IPv4, 16 for IPv6
|
||||
if (strlen($net) != strlen($addr)) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($mask)) {
|
||||
$mask = $length * 8;
|
||||
}
|
||||
$addr_bin = '';
|
||||
$net_bin = '';
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT);
|
||||
$net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask);
|
||||
}
|
||||
|
||||
if (isset($_GET['host'])) {
|
||||
try {
|
||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
if (in_net($_GET['host'], $host)) {
|
||||
echo '200 PERMIT';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
echo '200 DUNNO';
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
echo '200 DUNNO';
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
echo '240.240.240.240' . PHP_EOL;
|
||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
echo $host . PHP_EOL;
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
echo '240.240.240.240' . PHP_EOL;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
2
data/conf/rspamd/dynmaps/index.html
Executable file
2
data/conf/rspamd/dynmaps/index.html
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
<html>
|
||||
</html>
|
||||
2
data/conf/rspamd/dynmaps/sasl_logs.php
Executable file
2
data/conf/rspamd/dynmaps/sasl_logs.php
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
<?php
|
||||
// PoC
|
||||
471
data/conf/rspamd/dynmaps/settings.php
Executable file
471
data/conf/rspamd/dynmaps/settings.php
Executable file
|
|
@ -0,0 +1,471 @@
|
|||
<?php
|
||||
/*
|
||||
The match section performs AND operation on different matches: for example, if you have from and rcpt in the same rule,
|
||||
then the rule matches only when from AND rcpt match. For similar matches, the OR rule applies: if you have multiple rcpt matches,
|
||||
then any of these will trigger the rule. If a rule is triggered then no more rules are matched.
|
||||
*/
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Getting headers sent by the client.
|
||||
ini_set('error_reporting', 0);
|
||||
|
||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
$stmt = $pdo->query("SELECT '1' FROM `filterconf`");
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
echo 'settings { }';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if db changed and return header
|
||||
$stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables`
|
||||
WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias')
|
||||
AND TABLE_SCHEMA = :dbname;");
|
||||
$stmt->execute(array(
|
||||
':dbname' => $database_name
|
||||
));
|
||||
$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time'];
|
||||
if (empty($db_update_time)) {
|
||||
$db_update_time = 1572048000;
|
||||
}
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) {
|
||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304);
|
||||
exit;
|
||||
} else {
|
||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200);
|
||||
}
|
||||
|
||||
function parse_email($email) {
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||
$a = strrpos($email, '@');
|
||||
return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a));
|
||||
}
|
||||
|
||||
function normalize_email($email) {
|
||||
$email = strtolower(str_replace('/', '\/', $email));
|
||||
$gm = "@gmail.com";
|
||||
if (substr_compare($email, $gm, -strlen($gm)) == 0) {
|
||||
$email = explode('@', $email);
|
||||
$email[0] = str_replace('.', '', $email[0]);
|
||||
$email = implode('@', $email);
|
||||
}
|
||||
$gm_alt = "@googlemail.com";
|
||||
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
||||
$email = explode('@', $email);
|
||||
$email[0] = str_replace('.', '', $email[0]);
|
||||
$email[1] = str_replace('@', '', $gm);
|
||||
$email = implode('@', $email);
|
||||
}
|
||||
if (str_contains($email, "+")) {
|
||||
$email = explode('@', $email);
|
||||
$user = explode('+', $email[0]);
|
||||
$email[0] = $user[0];
|
||||
$email = implode('@', $email);
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
|
||||
function wl_by_sogo() {
|
||||
global $pdo;
|
||||
$rcpt = array();
|
||||
$stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info`
|
||||
INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id`
|
||||
GROUP BY `c_path2`");
|
||||
$sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($sogo_contacts)) {
|
||||
foreach (explode(',', $row['contacts']) as $contact) {
|
||||
if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) {
|
||||
continue;
|
||||
}
|
||||
// Explicit from, no mime_from, no regex - envelope must match
|
||||
// mailcow white and blacklists also cover mime_from
|
||||
$rcpt[$row['user']][] = normalize_email($contact);
|
||||
}
|
||||
}
|
||||
return $rcpt;
|
||||
}
|
||||
|
||||
function ucl_rcpts($object, $type) {
|
||||
global $pdo;
|
||||
$rcpt = array();
|
||||
if ($type == 'mailbox') {
|
||||
// Standard aliases
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
|
||||
WHERE `goto` = :object_goto
|
||||
AND `address` NOT LIKE '@%'");
|
||||
$stmt->execute(array(
|
||||
':object_goto' => $object
|
||||
));
|
||||
$standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($standard_aliases)) {
|
||||
$local = parse_email($row['address'])['local'];
|
||||
$domain = parse_email($row['address'])['domain'];
|
||||
if (!empty($local) && !empty($domain)) {
|
||||
$rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
|
||||
}
|
||||
$rcpt[] = str_replace('/', '\/', $row['address']);
|
||||
}
|
||||
// Aliases by alias domains
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
||||
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(
|
||||
':object' => $object
|
||||
));
|
||||
$by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($by_domain_aliases);
|
||||
while ($row = array_shift($by_domain_aliases)) {
|
||||
if (!empty($row['alias'])) {
|
||||
$local = parse_email($row['alias'])['local'];
|
||||
$domain = parse_email($row['alias'])['domain'];
|
||||
if (!empty($local) && !empty($domain)) {
|
||||
$rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i';
|
||||
}
|
||||
$rcpt[] = str_replace('/', '\/', $row['alias']);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($type == 'domain') {
|
||||
// Domain self
|
||||
$rcpt[] = '/.*@' . $object . '/i';
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
|
||||
WHERE `target_domain` = :object");
|
||||
$stmt->execute(array(':object' => $object));
|
||||
$alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($alias_domains);
|
||||
while ($row = array_shift($alias_domains)) {
|
||||
$rcpt[] = '/.*@' . $row['alias_domain'] . '/i';
|
||||
}
|
||||
}
|
||||
return $rcpt;
|
||||
}
|
||||
?>
|
||||
settings {
|
||||
watchdog {
|
||||
priority = 10;
|
||||
rcpt_mime = "/null@localhost/i";
|
||||
from_mime = "/watchdog@localhost/i";
|
||||
apply "default" {
|
||||
symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"];
|
||||
want_spam = yes;
|
||||
actions {
|
||||
reject = 9999.0;
|
||||
greylist = 9998.0;
|
||||
"add header" = 9997.0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
/*
|
||||
// Start custom scores for users
|
||||
*/
|
||||
|
||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
while ($row = array_shift($rows)) {
|
||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
||||
?>
|
||||
score_<?=$username_sane;?> {
|
||||
priority = 4;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
||||
AND `object`= :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
|
||||
?>
|
||||
apply "default" {
|
||||
actions {
|
||||
reject = <?=$spamscore['highspamlevel'][0];?>;
|
||||
greylist = <?=$spamscore['lowspamlevel'][0] - 1;?>;
|
||||
"add header" = <?=$spamscore['lowspamlevel'][0];?>;
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php
|
||||
}
|
||||
|
||||
/*
|
||||
// Start SOGo contacts whitelist
|
||||
// Priority 4, lower than a domain whitelist (5) and lower than a mailbox whitelist (6)
|
||||
*/
|
||||
|
||||
foreach (wl_by_sogo() as $user => $contacts) {
|
||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user);
|
||||
?>
|
||||
whitelist_sogo_<?=$username_sane;?> {
|
||||
<?php
|
||||
foreach ($contacts as $contact) {
|
||||
?>
|
||||
from = <?=json_encode($contact, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
priority = 4;
|
||||
<?php
|
||||
foreach (ucl_rcpts($user, 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
SOGO_CONTACT = -99.0;
|
||||
}
|
||||
symbols [
|
||||
"SOGO_CONTACT"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
|
||||
/*
|
||||
// Start whitelist
|
||||
*/
|
||||
|
||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
||||
?>
|
||||
whitelist_<?=$username_sane;?> {
|
||||
<?php
|
||||
$list_items = array();
|
||||
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
|
||||
WHERE `object`= :object
|
||||
AND `option` = 'whitelist_from'");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($list_items as $item) {
|
||||
?>
|
||||
from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
||||
<?php
|
||||
}
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = 5;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = 6;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_WHITE = -999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_WHITE"
|
||||
]
|
||||
}
|
||||
whitelist_mime_<?=$username_sane;?> {
|
||||
<?php
|
||||
foreach ($list_items as $item) {
|
||||
?>
|
||||
from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
||||
<?php
|
||||
}
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = 5;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = 6;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_WHITE = -999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_WHITE"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
|
||||
/*
|
||||
// Start blacklist
|
||||
*/
|
||||
|
||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
||||
?>
|
||||
blacklist_<?=$username_sane;?> {
|
||||
<?php
|
||||
$list_items = array();
|
||||
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
|
||||
WHERE `object`= :object
|
||||
AND `option` = 'blacklist_from'");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($list_items as $item) {
|
||||
?>
|
||||
from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
||||
<?php
|
||||
}
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = 5;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = 6;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_BLACK = 999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_BLACK"
|
||||
]
|
||||
}
|
||||
blacklist_header_<?=$username_sane;?> {
|
||||
<?php
|
||||
foreach ($list_items as $item) {
|
||||
?>
|
||||
from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
|
||||
<?php
|
||||
}
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = 5;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = 6;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_BLACK = 999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_BLACK"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
|
||||
/*
|
||||
// Start traps
|
||||
*/
|
||||
|
||||
?>
|
||||
ham_trap {
|
||||
<?php
|
||||
foreach (ucl_rcpts('ham@localhost', 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
priority = 9;
|
||||
apply "default" {
|
||||
symbols_enabled = ["HISTORY_SAVE"];
|
||||
}
|
||||
symbols [
|
||||
"HAM_TRAP"
|
||||
]
|
||||
}
|
||||
|
||||
spam_trap {
|
||||
<?php
|
||||
foreach (ucl_rcpts('spam@localhost', 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
priority = 9;
|
||||
apply "default" {
|
||||
symbols_enabled = ["HISTORY_SAVE"];
|
||||
}
|
||||
symbols [
|
||||
"SPAM_TRAP"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
// Start additional content
|
||||
|
||||
$stmt = $pdo->query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']);
|
||||
?>
|
||||
additional_settings_<?=intval($row['id']);?> {
|
||||
<?php
|
||||
$content = preg_split('/\r\n|\r|\n/', $row['content']);
|
||||
foreach ($content as $line) {
|
||||
echo ' ' . $line . PHP_EOL;
|
||||
}
|
||||
?>
|
||||
}
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
}
|
||||
6
data/conf/rspamd/dynmaps/vars.inc.php
Executable file
6
data/conf/rspamd/dynmaps/vars.inc.php
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
require_once('../../../web/inc/vars.inc.php');
|
||||
if (file_exists('../../../web/inc/vars.local.inc.php')) {
|
||||
include_once('../../../web/inc/vars.local.inc.php');
|
||||
}
|
||||
?>
|
||||
16
data/conf/rspamd/lua/ratelimit.lua
Executable file
16
data/conf/rspamd/lua/ratelimit.lua
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
local custom_keywords = {}
|
||||
|
||||
custom_keywords.mailcow = function(task)
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local dyn_rl_symbol = task:get_symbol("DYN_RL")
|
||||
if dyn_rl_symbol then
|
||||
local rl_value = dyn_rl_symbol[1].options[1]
|
||||
local rl_object = dyn_rl_symbol[1].options[2]
|
||||
if rl_value and rl_object then
|
||||
rspamd_logger.infox(rspamd_config, "DYN_RL symbol has value %s for object %s, returning %s...", rl_value, rl_object, "rs_dynrl_" .. rl_object)
|
||||
return "rs_dynrl_" .. rl_object, rl_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return custom_keywords
|
||||
701
data/conf/rspamd/lua/rspamd.local.lua
Executable file
701
data/conf/rspamd/lua/rspamd.local.lua
Executable file
|
|
@ -0,0 +1,701 @@
|
|||
rspamd_config.MAILCOW_AUTH = {
|
||||
callback = function(task)
|
||||
local uname = task:get_user()
|
||||
if uname then
|
||||
return 1
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
local monitoring_hosts = rspamd_config:add_map{
|
||||
url = "/etc/rspamd/custom/monitoring_nolog.map",
|
||||
description = "Monitoring hosts",
|
||||
type = "regexp"
|
||||
}
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'SMTP_ACCESS',
|
||||
type = 'postfilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_ip = require 'rspamd_ip'
|
||||
local uname = task:get_user()
|
||||
local limited_access = task:get_symbol("SMTP_LIMITED_ACCESS")
|
||||
|
||||
if not uname then
|
||||
return false
|
||||
end
|
||||
|
||||
if not limited_access then
|
||||
return false
|
||||
end
|
||||
|
||||
local hash_key = 'SMTP_ALLOW_NETS_' .. uname
|
||||
|
||||
local redis_params = rspamd_parse_redis_server('smtp_access')
|
||||
local ip = task:get_from_ip()
|
||||
|
||||
if ip == nil or not ip:is_valid() then
|
||||
return false
|
||||
end
|
||||
|
||||
local from_ip_string = tostring(ip)
|
||||
smtp_access_table = {from_ip_string}
|
||||
|
||||
local maxbits = 128
|
||||
local minbits = 32
|
||||
if ip:get_version() == 4 then
|
||||
maxbits = 32
|
||||
minbits = 8
|
||||
end
|
||||
for i=maxbits,minbits,-1 do
|
||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
||||
table.insert(smtp_access_table, nip)
|
||||
end
|
||||
local function smtp_access_cb(err, data)
|
||||
if err then
|
||||
rspamd_logger.infox(rspamd_config, "smtp_access query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
||||
return false
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "checking ip %s for smtp_access in %s", from_ip_string, hash_key)
|
||||
for k,v in pairs(data) do
|
||||
if (v and v ~= userdata and v == '1') then
|
||||
rspamd_logger.infox(rspamd_config, "found ip in smtp_access map")
|
||||
task:insert_result(true, 'SMTP_ACCESS', 0.0, from_ip_string)
|
||||
return true
|
||||
end
|
||||
end
|
||||
rspamd_logger.infox(rspamd_config, "couldnt find ip in smtp_access map")
|
||||
task:insert_result(true, 'SMTP_ACCESS', 999.0, from_ip_string)
|
||||
return true
|
||||
end
|
||||
end
|
||||
table.insert(smtp_access_table, 1, hash_key)
|
||||
local redis_ret_user = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
hash_key, -- hash key
|
||||
false, -- is write
|
||||
smtp_access_cb, --callback
|
||||
'HMGET', -- command
|
||||
smtp_access_table -- arguments
|
||||
)
|
||||
if not redis_ret_user then
|
||||
rspamd_logger.infox(rspamd_config, "cannot check smtp_access redis map")
|
||||
end
|
||||
end,
|
||||
priority = 10
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'POSTMASTER_HANDLER',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local rcpts = task:get_recipients('smtp')
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local lua_util = require "lua_util"
|
||||
local from = task:get_from(1)
|
||||
|
||||
-- not applying to mails with more than one rcpt to avoid bypassing filters by addressing postmaster
|
||||
if rcpts and #rcpts == 1 then
|
||||
for _,rcpt in ipairs(rcpts) do
|
||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
||||
if #rcpt_split == 2 then
|
||||
if rcpt_split[1] == 'postmaster' then
|
||||
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if from then
|
||||
for _,fr in ipairs(from) do
|
||||
local fr_split = rspamd_str_split(fr['addr'], '@')
|
||||
if #fr_split == 2 then
|
||||
if fr_split[1] == 'postmaster' and task:get_user() then
|
||||
-- no whitelist, keep signatures
|
||||
task:insert_result(true, 'POSTMASTER_FROM', -2500.0)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end,
|
||||
priority = 10
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'KEEP_SPAM',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_ip = require 'rspamd_ip'
|
||||
local uname = task:get_user()
|
||||
|
||||
if uname then
|
||||
return false
|
||||
end
|
||||
|
||||
local redis_params = rspamd_parse_redis_server('keep_spam')
|
||||
local ip = task:get_from_ip()
|
||||
|
||||
if ip == nil or not ip:is_valid() then
|
||||
return false
|
||||
end
|
||||
|
||||
local from_ip_string = tostring(ip)
|
||||
ip_check_table = {from_ip_string}
|
||||
|
||||
local maxbits = 128
|
||||
local minbits = 32
|
||||
if ip:get_version() == 4 then
|
||||
maxbits = 32
|
||||
minbits = 8
|
||||
end
|
||||
for i=maxbits,minbits,-1 do
|
||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
||||
table.insert(ip_check_table, nip)
|
||||
end
|
||||
local function keep_spam_cb(err, data)
|
||||
if err then
|
||||
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
||||
return false
|
||||
else
|
||||
for k,v in pairs(data) do
|
||||
if (v and v ~= userdata and v == '1') then
|
||||
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
||||
task:set_pre_result('accept', 'ip matched with forward hosts')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
||||
local redis_ret_user = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
'KEEP_SPAM', -- hash key
|
||||
false, -- is write
|
||||
keep_spam_cb, --callback
|
||||
'HMGET', -- command
|
||||
ip_check_table -- arguments
|
||||
)
|
||||
if not redis_ret_user then
|
||||
rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map")
|
||||
end
|
||||
end,
|
||||
priority = 19
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'TLS_HEADER',
|
||||
type = 'postfilter',
|
||||
callback = function(task)
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local tls_tag = task:get_request_header('TLS-Version')
|
||||
if type(tls_tag) == 'nil' then
|
||||
task:set_milter_reply({
|
||||
add_headers = {['X-Last-TLS-Session-Version'] = 'None'}
|
||||
})
|
||||
else
|
||||
task:set_milter_reply({
|
||||
add_headers = {['X-Last-TLS-Session-Version'] = tostring(tls_tag)}
|
||||
})
|
||||
end
|
||||
end,
|
||||
priority = 12
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'TAG_MOO',
|
||||
type = 'postfilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local redis_params = rspamd_parse_redis_server('taghandler')
|
||||
local rspamd_http = require "rspamd_http"
|
||||
local rcpts = task:get_recipients('smtp')
|
||||
local lua_util = require "lua_util"
|
||||
|
||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||
|
||||
local function remove_moo_tag()
|
||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||
if moo_tag_header then
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['X-Moo-Tag'] = 0},
|
||||
})
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
||||
local tag = tagged_rcpt[1].options[1]
|
||||
rspamd_logger.infox("found tag: %s", tag)
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("metric action now: %s", action)
|
||||
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
||||
remove_moo_tag()
|
||||
return true
|
||||
end
|
||||
|
||||
local function http_callback(err_message, code, body, headers)
|
||||
if body ~= nil and body ~= "" then
|
||||
rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
|
||||
|
||||
local function tag_callback_subject(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
||||
|
||||
local function tag_callback_subfolder(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_logger.infox("Add X-Moo-Tag header")
|
||||
task:set_milter_reply({
|
||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subfolder, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subfolder then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
end
|
||||
|
||||
else
|
||||
rspamd_logger.infox("user wants subject modified for tagged mail")
|
||||
local sbj = task:get_header('Subject')
|
||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||
task:set_milter_reply({
|
||||
remove_headers = {
|
||||
['Subject'] = 1,
|
||||
['X-Moo-Tag'] = 0
|
||||
},
|
||||
add_headers = {['Subject'] = new_sbj}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_subject = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subject, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subject then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if rcpts and #rcpts == 1 then
|
||||
for _,rcpt in ipairs(rcpts) do
|
||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
||||
if #rcpt_split == 2 then
|
||||
if rcpt_split[1] == 'postmaster' then
|
||||
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/aliasexp.php',
|
||||
body='',
|
||||
callback=http_callback,
|
||||
headers={Rcpt=rcpt['addr']},
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
remove_moo_tag()
|
||||
end
|
||||
end,
|
||||
priority = 19
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'BCC',
|
||||
type = 'postfilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_http = require "rspamd_http"
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
|
||||
local from_table = {}
|
||||
local rcpt_table = {}
|
||||
|
||||
if task:has_symbol('ENCRYPTED_CHAT') then
|
||||
return -- stop
|
||||
end
|
||||
|
||||
local send_mail = function(task, bcc_dest)
|
||||
local lua_smtp = require "lua_smtp"
|
||||
local function sendmail_cb(ret, err)
|
||||
if not ret then
|
||||
rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err)
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest)
|
||||
end
|
||||
end
|
||||
if not bcc_dest then
|
||||
return -- stop
|
||||
end
|
||||
-- dot stuff content before sending
|
||||
local email_content = tostring(task:get_content())
|
||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||
-- send mail
|
||||
lua_smtp.sendmail({
|
||||
task = task,
|
||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||
port = 591,
|
||||
from = task:get_from(stp)[1].addr,
|
||||
recipients = bcc_dest,
|
||||
helo = 'bcc',
|
||||
timeout = 20,
|
||||
}, email_content, sendmail_cb)
|
||||
end
|
||||
|
||||
-- determine from
|
||||
local from = task:get_from('smtp')
|
||||
if from then
|
||||
for _, a in ipairs(from) do
|
||||
table.insert(from_table, a['addr']) -- add this rcpt to table
|
||||
table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table
|
||||
end
|
||||
else
|
||||
return -- stop
|
||||
end
|
||||
|
||||
-- determine rcpts
|
||||
local rcpts = task:get_recipients('smtp')
|
||||
if rcpts then
|
||||
for _, a in ipairs(rcpts) do
|
||||
table.insert(rcpt_table, a['addr']) -- add this rcpt to table
|
||||
table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table
|
||||
end
|
||||
else
|
||||
return -- stop
|
||||
end
|
||||
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("metric action now: %s", action)
|
||||
|
||||
local function rcpt_callback(err_message, code, body, headers)
|
||||
if err_message == nil and code == 201 and body ~= nil then
|
||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||
send_mail(task, body)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function from_callback(err_message, code, body, headers)
|
||||
if err_message == nil and code == 201 and body ~= nil then
|
||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||
send_mail(task, body)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if rcpt_table then
|
||||
for _,e in ipairs(rcpt_table) do
|
||||
rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e)
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/bcc.php',
|
||||
body='',
|
||||
callback=rcpt_callback,
|
||||
headers={Rcpt=e}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if from_table then
|
||||
for _,e in ipairs(from_table) do
|
||||
rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e)
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/bcc.php',
|
||||
body='',
|
||||
callback=from_callback,
|
||||
headers={From=e}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
priority = 20
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'DYN_RL_CHECK',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local envfrom = task:get_from(1)
|
||||
local uname = task:get_user()
|
||||
if not envfrom or not uname then
|
||||
return false
|
||||
end
|
||||
local uname = uname:lower()
|
||||
|
||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
||||
|
||||
local function redis_cb_user(err, data)
|
||||
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", uname, data, err)
|
||||
|
||||
local function redis_key_cb_domain(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err)
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data)
|
||||
task:insert_result('DYN_RL', 0.0, data, env_from_domain)
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_domain = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
env_from_domain, -- hash key
|
||||
false, -- is write
|
||||
redis_key_cb_domain, --callback
|
||||
'HGET', -- command
|
||||
{'RL_VALUE', env_from_domain} -- arguments
|
||||
)
|
||||
if not redis_ret_domain then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain")
|
||||
end
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", uname, data)
|
||||
task:insert_result('DYN_RL', 0.0, data, uname)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local redis_ret_user = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
uname, -- hash key
|
||||
false, -- is write
|
||||
redis_cb_user, --callback
|
||||
'HGET', -- command
|
||||
{'RL_VALUE', uname} -- arguments
|
||||
)
|
||||
if not redis_ret_user then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user")
|
||||
end
|
||||
return true
|
||||
end,
|
||||
flags = 'empty',
|
||||
priority = 20
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'NO_LOG_STAT',
|
||||
type = 'postfilter',
|
||||
callback = function(task)
|
||||
local from = task:get_header('From')
|
||||
if from and (monitoring_hosts:get_key(from) or from == "watchdog@localhost") then
|
||||
task:set_flag('no_log')
|
||||
task:set_flag('no_stat')
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'MOO_FOOTER',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local cjson = require "cjson"
|
||||
local lua_mime = require "lua_mime"
|
||||
local lua_util = require "lua_util"
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local rspamd_http = require "rspamd_http"
|
||||
local envfrom = task:get_from(1)
|
||||
local uname = task:get_user()
|
||||
if not envfrom or not uname then
|
||||
return false
|
||||
end
|
||||
local uname = uname:lower()
|
||||
local env_from_domain = envfrom[1].domain:lower()
|
||||
local env_from_addr = envfrom[1].addr:lower()
|
||||
|
||||
-- determine newline type
|
||||
local function newline(task)
|
||||
local t = task:get_newlines_type()
|
||||
|
||||
if t == 'cr' then
|
||||
return '\r'
|
||||
elseif t == 'lf' then
|
||||
return '\n'
|
||||
end
|
||||
|
||||
return '\r\n'
|
||||
end
|
||||
-- retrieve footer
|
||||
local function footer_cb(err_message, code, data, headers)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
|
||||
-- parse json string
|
||||
local footer = cjson.decode(data)
|
||||
if not footer then
|
||||
rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||
else
|
||||
if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then
|
||||
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars)
|
||||
|
||||
if footer.skip_replies ~= 0 then
|
||||
in_reply_to = task:get_header_raw('in-reply-to')
|
||||
if in_reply_to then
|
||||
rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local envfrom_mime = task:get_from(2)
|
||||
local from_name = ""
|
||||
if envfrom_mime and envfrom_mime[1].name then
|
||||
from_name = envfrom_mime[1].name
|
||||
elseif envfrom and envfrom[1].name then
|
||||
from_name = envfrom[1].name
|
||||
end
|
||||
|
||||
-- default replacements
|
||||
local replacements = {
|
||||
auth_user = uname,
|
||||
from_user = envfrom[1].user,
|
||||
from_name = from_name,
|
||||
from_addr = envfrom[1].addr,
|
||||
from_domain = envfrom[1].domain:lower()
|
||||
}
|
||||
-- add custom mailbox attributes
|
||||
if footer.vars and type(footer.vars) == "string" then
|
||||
local footer_vars = cjson.decode(footer.vars)
|
||||
|
||||
if type(footer_vars) == "table" then
|
||||
for key, value in pairs(footer_vars) do
|
||||
replacements[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
if footer.html and footer.html ~= "" then
|
||||
footer.html = lua_util.jinja_template(footer.html, replacements, true)
|
||||
end
|
||||
if footer.plain and footer.plain ~= "" then
|
||||
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
||||
end
|
||||
|
||||
-- add footer
|
||||
local out = {}
|
||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
||||
|
||||
local seen_cte
|
||||
local newline_s = newline(task)
|
||||
|
||||
local function rewrite_ct_cb(name, hdr)
|
||||
if rewrite.need_rewrite_ct then
|
||||
if name:lower() == 'content-type' then
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
||||
out[#out + 1] = nct
|
||||
-- update Content-Type header
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Content-Type'] = 0},
|
||||
})
|
||||
task:set_milter_reply({
|
||||
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8', rewrite.new_ct.type, rewrite.new_ct.subtype)}
|
||||
})
|
||||
return
|
||||
elseif name:lower() == 'content-transfer-encoding' then
|
||||
out[#out + 1] = string.format('%s: %s',
|
||||
'Content-Transfer-Encoding', 'quoted-printable')
|
||||
-- update Content-Transfer-Encoding header
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Content-Transfer-Encoding'] = 0},
|
||||
})
|
||||
task:set_milter_reply({
|
||||
add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'}
|
||||
})
|
||||
seen_cte = true
|
||||
return
|
||||
end
|
||||
end
|
||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
||||
end
|
||||
|
||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||
|
||||
if not seen_cte and rewrite.need_rewrite_ct then
|
||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
||||
end
|
||||
|
||||
-- End of headers
|
||||
out[#out + 1] = newline_s
|
||||
|
||||
if rewrite.out then
|
||||
for _,o in ipairs(rewrite.out) do
|
||||
out[#out + 1] = o
|
||||
end
|
||||
else
|
||||
out[#out + 1] = task:get_rawbody()
|
||||
end
|
||||
local out_parts = {}
|
||||
for _,o in ipairs(out) do
|
||||
if type(o) ~= 'table' then
|
||||
out_parts[#out_parts + 1] = o
|
||||
out_parts[#out_parts + 1] = newline_s
|
||||
else
|
||||
local removePrefix = "--\x0D\x0AContent-Type"
|
||||
if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then
|
||||
o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1)
|
||||
end
|
||||
out_parts[#out_parts + 1] = o[1]
|
||||
if o[2] then
|
||||
out_parts[#out_parts + 1] = newline_s
|
||||
end
|
||||
end
|
||||
end
|
||||
task:set_message(out_parts)
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- fetch footer
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/footer.php',
|
||||
body='',
|
||||
callback=footer_cb,
|
||||
headers={Domain=env_from_domain,Username=uname,From=env_from_addr},
|
||||
})
|
||||
|
||||
return true
|
||||
end,
|
||||
priority = 1
|
||||
})
|
||||
260
data/conf/rspamd/meta_exporter/pipe.php
Executable file
260
data/conf/rspamd/meta_exporter/pipe.php
Executable file
|
|
@ -0,0 +1,260 @@
|
|||
<?php
|
||||
// File size is limited by Nginx site to 10M
|
||||
// To speed things up, we do not include prerequisites
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init database
|
||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
|
||||
// Functions
|
||||
function parse_email($email) {
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||
$a = strrpos($email, '@');
|
||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
||||
}
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
if (!is_array($_SERVER)) {
|
||||
return array();
|
||||
}
|
||||
$headers = array();
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
||||
$raw_data_content = file_get_contents('php://input');
|
||||
$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
|
||||
$headers = getallheaders();
|
||||
|
||||
$qid = $headers['X-Rspamd-Qid'];
|
||||
$fuzzy = $headers['X-Rspamd-Fuzzy'];
|
||||
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
|
||||
$score = $headers['X-Rspamd-Score'];
|
||||
$rcpts = $headers['X-Rspamd-Rcpt'];
|
||||
$user = $headers['X-Rspamd-User'];
|
||||
$ip = $headers['X-Rspamd-Ip'];
|
||||
$action = $headers['X-Rspamd-Action'];
|
||||
$sender = $headers['X-Rspamd-From'];
|
||||
$symbols = $headers['X-Rspamd-Symbols'];
|
||||
|
||||
$raw_size = (int)$_SERVER['CONTENT_LENGTH'];
|
||||
|
||||
if (empty($sender)) {
|
||||
error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL);
|
||||
$sender = 'empty-env-from@localhost';
|
||||
}
|
||||
|
||||
if ($fuzzy == 'unknown') {
|
||||
$fuzzy = '[]';
|
||||
}
|
||||
|
||||
try {
|
||||
$max_size = (int)$redis->Get('Q_MAX_SIZE');
|
||||
if (($max_size * 1048576) < $raw_size) {
|
||||
error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL);
|
||||
http_response_code(505);
|
||||
exit;
|
||||
}
|
||||
if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) {
|
||||
$exclude_domains = json_decode($exclude_domains, true);
|
||||
}
|
||||
$retention_size = (int)$redis->Get('Q_RETENTION_SIZE');
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
||||
http_response_code(504);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rcpt_final_mailboxes = array();
|
||||
|
||||
// Loop through all rcpts
|
||||
foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
// Remove tag
|
||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
||||
|
||||
// Break rcpt into local part and domain part
|
||||
$parsed_rcpt = parse_email($rcpt);
|
||||
|
||||
// Skip if not a mailcow handled domain
|
||||
try {
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
error_log("QUARANTINE: " . $e . PHP_EOL);
|
||||
http_response_code(504);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Skip if domain is excluded
|
||||
if (in_array($parsed_rcpt['domain'], $exclude_domains)) {
|
||||
error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
|
||||
//
|
||||
// rcpt
|
||||
// |
|
||||
// mailbox <-- goto ---> alias1, alias2, mailbox2
|
||||
// | |
|
||||
// mailbox3 |
|
||||
// |
|
||||
// alias3 ---> mailbox4
|
||||
//
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':rcpt' => $rcpt
|
||||
));
|
||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
if (empty($gotos)) {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':rcpt' => '@' . $parsed_rcpt['domain']
|
||||
));
|
||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
}
|
||||
if (empty($gotos)) {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(':rcpt' => $parsed_rcpt['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
$gotos = $parsed_rcpt['local'] . '@' . $goto_branch;
|
||||
}
|
||||
}
|
||||
$gotos_array = explode(',', $gotos);
|
||||
|
||||
$loop_c = 0;
|
||||
|
||||
while (count($gotos_array) != 0 && $loop_c <= 20) {
|
||||
|
||||
// Loop through all found gotos
|
||||
foreach ($gotos_array as $index => &$goto) {
|
||||
error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
||||
$stmt->execute(array(':goto' => $goto));
|
||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
||||
if (!empty($username)) {
|
||||
error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
|
||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
||||
$rcpt_final_mailboxes[] = $username;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$parsed_goto = parse_email($goto);
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
||||
$stmt->execute(array(':goto' => $goto));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
if ($goto_branch) {
|
||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// goto item was processed, unset
|
||||
unset($gotos_array[$index]);
|
||||
}
|
||||
|
||||
// Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
|
||||
if (!empty($goto_branch_array)) {
|
||||
$gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
|
||||
unset($goto_branch_array);
|
||||
}
|
||||
|
||||
// Reindex array
|
||||
$gotos_array = array_values($gotos_array);
|
||||
|
||||
// Force exit if loop cannot be solved
|
||||
// Postfix does not allow for alias loops, so this should never happen.
|
||||
$loop_c++;
|
||||
error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(502);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
||||
error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt_final . PHP_EOL);
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`, `fuzzy_hashes`)
|
||||
VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action, :fuzzy_hashes)");
|
||||
$stmt->execute(array(
|
||||
':qid' => $qid,
|
||||
':subject' => $subject,
|
||||
':score' => $score,
|
||||
':sender' => $sender,
|
||||
':rcpt' => $rcpt_final,
|
||||
':symbols' => $symbols,
|
||||
':user' => $user,
|
||||
':ip' => $ip,
|
||||
':msg' => $raw_data,
|
||||
':action' => $action,
|
||||
':fuzzy_hashes' => $fuzzy
|
||||
));
|
||||
$stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
|
||||
SELECT `id`
|
||||
FROM (
|
||||
SELECT `id`
|
||||
FROM `quarantine`
|
||||
WHERE `rcpt` = :rcpt2
|
||||
ORDER BY id DESC
|
||||
LIMIT :retention_size
|
||||
) x
|
||||
);');
|
||||
$stmt->execute(array(
|
||||
':rcpt' => $rcpt_final,
|
||||
':rcpt2' => $rcpt_final,
|
||||
':retention_size' => $retention_size
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("QUARANTINE: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(503);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
48
data/conf/rspamd/meta_exporter/pipe_rl.php
Executable file
48
data/conf/rspamd/meta_exporter/pipe_rl.php
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
// File size is limited by Nginx site to 10M
|
||||
// To speed things up, we do not include prerequisites
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
try {
|
||||
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
|
||||
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
|
||||
}
|
||||
else {
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$raw_data_content = file_get_contents('php://input');
|
||||
$raw_data_decoded = json_decode($raw_data_content, true);
|
||||
|
||||
$data['time'] = time();
|
||||
$data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']);
|
||||
$data['from'] = $raw_data_decoded['from'];
|
||||
$data['user'] = $raw_data_decoded['user'];
|
||||
$symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name'));
|
||||
$data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']);
|
||||
preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches);
|
||||
if (!empty($rl_matches[1]) && !empty($rl_matches[2])) {
|
||||
$data['rl_name'] = $rl_matches[1];
|
||||
$data['rl_hash'] = $rl_matches[2];
|
||||
}
|
||||
else {
|
||||
$data['rl_name'] = 'err';
|
||||
$data['rl_hash'] = 'err';
|
||||
}
|
||||
$data['qid'] = $raw_data_decoded['qid'];
|
||||
$data['ip'] = $raw_data_decoded['ip'];
|
||||
$data['message_id'] = $raw_data_decoded['message_id'];
|
||||
$data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']);
|
||||
$data['header_from'] = implode(', ', $raw_data_decoded['header_from']);
|
||||
|
||||
$redis->lpush('RL_LOG', json_encode($data));
|
||||
exit;
|
||||
|
||||
275
data/conf/rspamd/meta_exporter/pushover.php
Executable file
275
data/conf/rspamd/meta_exporter/pushover.php
Executable file
|
|
@ -0,0 +1,275 @@
|
|||
<?php
|
||||
// File size is limited by Nginx site to 10M
|
||||
// To speed things up, we do not include prerequisites
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
// Do not show errors, we log to using error_log
|
||||
ini_set('error_reporting', 0);
|
||||
// Init database
|
||||
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("NOTIFY: " . $e . PHP_EOL);
|
||||
http_response_code(501);
|
||||
exit;
|
||||
}
|
||||
// Init Redis
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
|
||||
// Functions
|
||||
function parse_email($email) {
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
|
||||
$a = strrpos($email, '@');
|
||||
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
|
||||
}
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders() {
|
||||
if (!is_array($_SERVER)) {
|
||||
return array();
|
||||
}
|
||||
$headers = array();
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
||||
$headers = getallheaders();
|
||||
$json_body = json_decode(file_get_contents('php://input'));
|
||||
|
||||
$qid = $headers['X-Rspamd-Qid'];
|
||||
$rcpts = $headers['X-Rspamd-Rcpt'];
|
||||
$sender = $headers['X-Rspamd-From'];
|
||||
$ip = $headers['X-Rspamd-Ip'];
|
||||
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
|
||||
$messageid= $json_body->message_id;
|
||||
$priority = 0;
|
||||
|
||||
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true);
|
||||
if (is_array($symbols_array)) {
|
||||
foreach ($symbols_array as $symbol) {
|
||||
if ($symbol['name'] == 'HAS_X_PRIO_ONE') {
|
||||
$priority = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sender_address = $json_body->header_from[0];
|
||||
$sender_name = '-';
|
||||
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
|
||||
$sender_address = $matches['address'];
|
||||
$sender_name = trim($matches['name'], '"\' ');
|
||||
}
|
||||
|
||||
$to_address = $json_body->header_to[0];
|
||||
$to_name = '-';
|
||||
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
|
||||
$to_address = $matches['address'];
|
||||
$to_name = trim($matches['name'], '"\' ');
|
||||
}
|
||||
|
||||
$rcpt_final_mailboxes = array();
|
||||
|
||||
// Loop through all rcpts
|
||||
foreach (json_decode($rcpts, true) as $rcpt) {
|
||||
// Remove tag
|
||||
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
|
||||
|
||||
// Break rcpt into local part and domain part
|
||||
$parsed_rcpt = parse_email($rcpt);
|
||||
|
||||
// Skip if not a mailcow handled domain
|
||||
try {
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
error_log("NOTIFY: " . $e . PHP_EOL);
|
||||
http_response_code(504);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
|
||||
//
|
||||
// rcpt
|
||||
// |
|
||||
// mailbox <-- goto ---> alias1, alias2, mailbox2
|
||||
// | |
|
||||
// mailbox3 |
|
||||
// |
|
||||
// alias3 ---> mailbox4
|
||||
//
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':rcpt' => $rcpt
|
||||
));
|
||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
if (empty($gotos)) {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':rcpt' => '@' . $parsed_rcpt['domain']
|
||||
));
|
||||
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
}
|
||||
if (empty($gotos)) {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'");
|
||||
$stmt->execute(array(':rcpt' => $parsed_rcpt['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
$gotos = $parsed_rcpt['local'] . '@' . $goto_branch;
|
||||
}
|
||||
}
|
||||
$gotos_array = explode(',', $gotos);
|
||||
|
||||
$loop_c = 0;
|
||||
|
||||
while (count($gotos_array) != 0 && $loop_c <= 20) {
|
||||
|
||||
// Loop through all found gotos
|
||||
foreach ($gotos_array as $index => &$goto) {
|
||||
error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL);
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');");
|
||||
$stmt->execute(array(':goto' => $goto));
|
||||
$username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
|
||||
if (!empty($username)) {
|
||||
error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL);
|
||||
// Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
|
||||
if (!in_array($username, $rcpt_final_mailboxes)) {
|
||||
$rcpt_final_mailboxes[] = $username;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$parsed_goto = parse_email($goto);
|
||||
if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
|
||||
error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL);
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
|
||||
$stmt->execute(array(':goto' => $goto));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
|
||||
if ($goto_branch) {
|
||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL);
|
||||
$goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// goto item was processed, unset
|
||||
unset($gotos_array[$index]);
|
||||
}
|
||||
|
||||
// Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
|
||||
if (!empty($goto_branch_array)) {
|
||||
$gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
|
||||
unset($goto_branch_array);
|
||||
}
|
||||
|
||||
// Reindex array
|
||||
$gotos_array = array_values($gotos_array);
|
||||
|
||||
// Force exit if loop cannot be solved
|
||||
// Postfix does not allow for alias loops, so this should never happen.
|
||||
$loop_c++;
|
||||
error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL);
|
||||
}
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL);
|
||||
http_response_code(502);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
||||
error_log("NOTIFY: pushover pipe: processing pushover message for rcpt " . $rcpt_final . PHP_EOL);
|
||||
$stmt = $pdo->prepare("SELECT * FROM `pushover`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(
|
||||
':username' => $rcpt_final
|
||||
));
|
||||
$api_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (isset($api_data['key']) && isset($api_data['token'])) {
|
||||
$title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail';
|
||||
$text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧';
|
||||
$attributes = json_decode($api_data['attributes'], true);
|
||||
$senders = explode(',', $api_data['senders']);
|
||||
$senders = array_filter($senders);
|
||||
$senders_regex = $api_data['senders_regex'];
|
||||
$sender_validated = false;
|
||||
if (empty($senders) && empty($senders_regex)) {
|
||||
$sender_validated = true;
|
||||
}
|
||||
else {
|
||||
if (!empty($senders)) {
|
||||
if (in_array($sender, $senders)) {
|
||||
$sender_validated = true;
|
||||
}
|
||||
}
|
||||
if (!empty($senders_regex) && $sender_validated !== true) {
|
||||
if (preg_match($senders_regex, $sender)) {
|
||||
$sender_validated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($sender_validated === false) {
|
||||
error_log("NOTIFY: pushover pipe: skipping unwanted sender " . $sender);
|
||||
continue;
|
||||
}
|
||||
if ($attributes['only_x_prio'] == "1" && $priority == 0) {
|
||||
error_log("NOTIFY: pushover pipe: mail has no X-Priority: 1 header, skipping");
|
||||
continue;
|
||||
}
|
||||
$post_fields = array(
|
||||
"token" => $api_data['token'],
|
||||
"user" => $api_data['key'],
|
||||
"title" => sprintf("%s", str_replace(
|
||||
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'),
|
||||
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title)
|
||||
),
|
||||
"priority" => $priority,
|
||||
"message" => sprintf("%s", str_replace(
|
||||
array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'),
|
||||
array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text)
|
||||
),
|
||||
"sound" => $attributes['sound'] ?? "pushover"
|
||||
);
|
||||
if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) {
|
||||
$post_fields['expire'] = 600;
|
||||
$post_fields['retry'] = 120;
|
||||
$post_fields['priority'] = 2;
|
||||
}
|
||||
curl_setopt_array($ch = curl_init(), array(
|
||||
CURLOPT_URL => "https://api.pushover.net/1/messages.json",
|
||||
CURLOPT_POSTFIELDS => $post_fields,
|
||||
CURLOPT_SAFE_UPLOAD => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
));
|
||||
$result = curl_exec($ch);
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
error_log("NOTIFY: result: " . $httpcode . PHP_EOL);
|
||||
}
|
||||
}
|
||||
6
data/conf/rspamd/meta_exporter/vars.inc.php
Executable file
6
data/conf/rspamd/meta_exporter/vars.inc.php
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
require_once('../../../web/inc/vars.inc.php');
|
||||
if (file_exists('../../../web/inc/vars.local.inc.php')) {
|
||||
include_once('../../../web/inc/vars.local.inc.php');
|
||||
}
|
||||
?>
|
||||
1
data/conf/rspamd/plugins.d/README.md
Executable file
1
data/conf/rspamd/plugins.d/README.md
Executable file
|
|
@ -0,0 +1 @@
|
|||
This is where you should copy any rspamd custom module
|
||||
1
data/conf/rspamd/rspamd.conf.local
Executable file
1
data/conf/rspamd/rspamd.conf.local
Executable file
|
|
@ -0,0 +1 @@
|
|||
# rspamd.conf.local
|
||||
2
data/conf/rspamd/rspamd.conf.override
Executable file
2
data/conf/rspamd/rspamd.conf.override
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
# rspamd.conf.override
|
||||
|
||||
BIN
data/conf/sogo/custom-favicon.ico
Executable file
BIN
data/conf/sogo/custom-favicon.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
7
data/conf/sogo/custom-sogo.js
Executable file
7
data/conf/sogo/custom-sogo.js
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
// Custom SOGo JS
|
||||
|
||||
// Change the visible font-size in the editor, this does not change the font of a html message by default
|
||||
CKEDITOR.addCss("body {font-size: 16px !important}");
|
||||
|
||||
// Enable scayt by default
|
||||
//CKEDITOR.config.scayt_autoStartup = true;
|
||||
98
data/conf/sogo/sogo.conf
Executable file
98
data/conf/sogo/sogo.conf
Executable file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
SOGoCalendarDefaultRoles = (
|
||||
PublicViewer,
|
||||
ConfidentialDAndTViewer,
|
||||
PrivateDAndTViewer
|
||||
);
|
||||
|
||||
WOWorkersCount = "20";
|
||||
SOGoACLsSendEMailNotifications = YES;
|
||||
SOGoAppointmentSendEMailNotifications = YES;
|
||||
SOGoDraftsFolderName = "Drafts";
|
||||
SOGoJunkFolderName= "Junk";
|
||||
SOGoMailDomain = "sogo.local";
|
||||
SOGoEnableEMailAlarms = YES;
|
||||
SOGoMailHideInlineAttachments = YES;
|
||||
SOGoFoldersSendEMailNotifications = YES;
|
||||
SOGoForwardEnabled = YES;
|
||||
|
||||
// Option to set Users as admin to globally manage calendar permissions etc. Disabled by default
|
||||
// SOGoSuperUsernames = ("moo@example.com");
|
||||
|
||||
SOGoUIAdditionalJSFiles = (
|
||||
js/theme.js,
|
||||
js/custom-sogo.js
|
||||
);
|
||||
|
||||
SOGoEnablePublicAccess = YES;
|
||||
|
||||
// Multi-domain setup
|
||||
// Domains are isolated, you can define visibility options here.
|
||||
// Example:
|
||||
|
||||
// SOGoDomainsVisibility = (
|
||||
// (domain1.tld, domain5.tld),
|
||||
// (domain3.tld, domain2.tld)
|
||||
// );
|
||||
|
||||
// self-signed is not trusted anymore
|
||||
WOPort = "0.0.0.0:20000";
|
||||
SOGoMemcachedHost = "memcached";
|
||||
|
||||
SOGoLanguage = English;
|
||||
SOGoMailAuxiliaryUserAccountsEnabled = YES;
|
||||
// SOGoCreateIdentitiesDisabled = NO;
|
||||
SOGoMailCustomFromEnabled = YES;
|
||||
SOGoMailingMechanism = smtp;
|
||||
SOGoSMTPAuthenticationType = plain;
|
||||
|
||||
SxVMemLimit = 384;
|
||||
|
||||
SOGoMaximumPingInterval = 3540;
|
||||
|
||||
SOGoInternalSyncInterval = 45;
|
||||
SOGoMaximumSyncInterval = 3540;
|
||||
|
||||
// 100 seems to break some Android clients
|
||||
//SOGoMaximumSyncWindowSize = 99;
|
||||
// This should do the trick for Outlook 2016
|
||||
SOGoMaximumSyncResponseSize = 512;
|
||||
|
||||
WOWatchDogRequestTimeout = 30;
|
||||
WOListenQueueSize = 16;
|
||||
WONoDetach = YES;
|
||||
|
||||
SOGoIMAPAclConformsToIMAPExt = Yes;
|
||||
SOGoPageTitle = "SOGo Groupware";
|
||||
SOGoFirstDayOfWeek = "1";
|
||||
|
||||
SOGoSieveFolderEncoding = "UTF-8";
|
||||
SOGoPasswordChangeEnabled = NO;
|
||||
SOGoSentFolderName = "Sent";
|
||||
SOGoMailShowSubscribedFoldersOnly = NO;
|
||||
NGImap4ConnectionStringSeparator = "/";
|
||||
SOGoSieveScriptsEnabled = YES;
|
||||
SOGoTrashFolderName = "Trash";
|
||||
SOGoVacationEnabled = YES;
|
||||
|
||||
SOGoCacheCleanupInterval = 900;
|
||||
SOGoMaximumFailedLoginCount = 10;
|
||||
SOGoMaximumFailedLoginInterval = 900;
|
||||
SOGoFailedLoginBlockInterval = 900;
|
||||
|
||||
GCSChannelCollectionTimer = 60;
|
||||
GCSChannelExpireAge = 60;
|
||||
|
||||
MySQL4Encoding = "utf8mb4";
|
||||
//SOGoDebugRequests = YES;
|
||||
//SoDebugBaseURL = YES;
|
||||
//ImapDebugEnabled = YES;
|
||||
//SOGoEASDebugEnabled = YES;
|
||||
SOGoEASSearchInBody = YES; // Experimental. Enabled since 2023-10
|
||||
//LDAPDebugEnabled = YES;
|
||||
//PGDebugEnabled = YES;
|
||||
//MySQL4DebugEnabled = YES;
|
||||
//SOGoUIxDebugEnabled = YES;
|
||||
//WODontZipResponse = YES;
|
||||
WOLogFile = "/dev/sogo_log";
|
||||
}
|
||||
45
data/conf/unbound/unbound.conf
Executable file
45
data/conf/unbound/unbound.conf
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
server:
|
||||
verbosity: 1
|
||||
interface: 0.0.0.0
|
||||
interface: ::0
|
||||
logfile: /dev/console
|
||||
do-ip4: yes
|
||||
do-ip6: yes
|
||||
do-udp: yes
|
||||
do-tcp: yes
|
||||
do-daemonize: no
|
||||
#access-control: 0.0.0.0/0 allow
|
||||
access-control: 10.0.0.0/8 allow
|
||||
access-control: 172.16.0.0/12 allow
|
||||
access-control: 192.168.0.0/16 allow
|
||||
access-control: fc00::/7 allow
|
||||
access-control: fe80::/10 allow
|
||||
#access-control: ::0/0 allow
|
||||
directory: "/etc/unbound"
|
||||
username: unbound
|
||||
auto-trust-anchor-file: trusted-key.key
|
||||
#private-address: 10.0.0.0/8
|
||||
#private-address: 172.16.0.0/12
|
||||
#private-address: 192.168.0.0/16
|
||||
#private-address: 169.254.0.0/16
|
||||
#private-address: fc00::/7
|
||||
#private-address: fe80::/10
|
||||
# cache-min-ttl needs to be less or equal to cache-max-negative-ttl
|
||||
cache-min-ttl: 5
|
||||
cache-max-negative-ttl: 60
|
||||
root-hints: "/etc/unbound/root.hints"
|
||||
hide-identity: yes
|
||||
hide-version: yes
|
||||
max-udp-size: 4096
|
||||
msg-buffer-size: 65552
|
||||
unwanted-reply-threshold: 10000
|
||||
ipsecmod-enabled: no
|
||||
|
||||
remote-control:
|
||||
control-enable: yes
|
||||
control-interface: 127.0.0.1
|
||||
control-port: 8953
|
||||
server-key-file: "/etc/unbound/unbound_server.key"
|
||||
server-cert-file: "/etc/unbound/unbound_server.pem"
|
||||
control-key-file: "/etc/unbound/unbound_control.key"
|
||||
control-cert-file: "/etc/unbound/unbound_control.pem"
|
||||
Loading…
Add table
Add a link
Reference in a new issue