Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41d9f0df73 | |||
| 279bdd003b | |||
| 5f6631176d | |||
| 21df0d8fd7 | |||
| 3c41283fed | |||
| 1b0b9d7604 | |||
| fd72237063 | |||
| 36f4f49ff1 | |||
| 081fac831d | |||
| c2d8263e51 | |||
| ab4d093b8e | |||
| 415b8d932f | |||
| 91237e1000 | |||
| 1e2da3603d | |||
| fedbb53942 | |||
| 0e670b786a | |||
| 3314c74a10 | |||
| e8ea2d06ef | |||
| 717aba1e48 | |||
| 73161ebae3 | |||
| 8bf84dc18d | |||
| 0cef2a76ea |
@@ -1,3 +1,2 @@
|
|||||||
rspamd-cgp
|
rspamd-cgp
|
||||||
*.CU*
|
*.CU*
|
||||||
vendor/github.com
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
|
|
||||||
Rspamd plugin for CommuniGate Pro 5.x, 6.x
|
Rspamd helper for CommuniGate Pro 5.x, 6.x
|
||||||
|
|
||||||
Installation and usage instructions can be found at
|
Copyright (C) 2017-2023 Andrey Igoshin <ai@vsu.ru>
|
||||||
http://www.communigate.com/CGPMcAfee/#Integrate
|
Version 1.5.0
|
||||||
|
|
||||||
|
|
||||||
Copyright (C) 2017-2021 Andrey Igoshin <ai@vsu.ru>
|
|
||||||
Version 1.3.0
|
|
||||||
|
|
||||||
https://git.vsu.ru/ai/rspamd-cgp
|
https://git.vsu.ru/ai/rspamd-cgp
|
||||||
|
|
||||||
@@ -16,7 +12,9 @@
|
|||||||
Authentication Identifier (default CommuniGate Pro Main Domain)
|
Authentication Identifier (default CommuniGate Pro Main Domain)
|
||||||
-host string
|
-host string
|
||||||
Rspamd host to connect (default "localhost:11333")
|
Rspamd host to connect (default "localhost:11333")
|
||||||
-reject-action string
|
-mirror-discard
|
||||||
Reject action: "add_header" or "discard" (default "add_header")
|
Mirror then discard selected messages
|
||||||
|
-mirror-to string
|
||||||
|
Mirror selected messages to email
|
||||||
-timeout duration
|
-timeout duration
|
||||||
Rspamd request timeout (default 15s)
|
Rspamd request timeout (default 15s)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export GOPATH="${HOME}/src/rspamd-cgp"
|
||||||
|
|
||||||
|
# если на целевой ОС не совпадает glibc, то собираем без зависимостей
|
||||||
|
# результирующий файл, скорее всего, получится медленнее и большего размера
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
|
if [ "$1" == "fmt" ]; then
|
||||||
|
go fmt $*
|
||||||
|
elif [ "$1" == "tidy" ]; then
|
||||||
|
go mod tidy
|
||||||
|
elif [ "$1" == "vet" ]; then
|
||||||
|
echo "vet..."
|
||||||
|
go vet
|
||||||
|
else
|
||||||
|
go build
|
||||||
|
fi
|
||||||
+331
-17
@@ -2,14 +2,23 @@ package cgp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const recvHdr = "Received:"
|
||||||
|
const seenHdr = "X-Rspamd-Seen:"
|
||||||
|
const subjHdr = "Subject:"
|
||||||
|
const submitDir = "Submitted"
|
||||||
|
|
||||||
var MainDomain string
|
var MainDomain string
|
||||||
var reMD *regexp.Regexp
|
var reMD *regexp.Regexp
|
||||||
var reSELF *regexp.Regexp
|
var reSELF *regexp.Regexp
|
||||||
@@ -27,11 +36,14 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddHeader(seq int, headers *string) {
|
func AddHeader(seq int, headers []string) {
|
||||||
|
|
||||||
|
hdrs := replaceSpecChars(strings.Join(headers, "\n"))
|
||||||
|
|
||||||
if protocol >= 4 {
|
if protocol >= 4 {
|
||||||
Putline("%d ADDHEADER \"%s\" OK\n", seq, *headers)
|
Putline("%d ADDHEADER \"%s\" OK\n", seq, hdrs)
|
||||||
} else {
|
} else {
|
||||||
Putline("%d ADDHEADER \"%s\"\n", seq, *headers)
|
Putline("%d ADDHEADER \"%s\"\n", seq, hdrs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,14 +61,14 @@ func Intf(seq int, ver string) {
|
|||||||
Putline("%d INTF %d\n", seq, protocol)
|
Putline("%d INTF %d\n", seq, protocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Message(filename *string) (from string, rcpts []string, auth string, ip string, qid int, body []byte, err error) {
|
func Message(filename string) (from string, rcpts []string, auth string, ip string, qid int, body []byte, seen bool, err error) {
|
||||||
|
|
||||||
qid, err = strconv.Atoi((*filename)[strings.LastIndexByte(*filename, '/')+1 : strings.LastIndexByte(*filename, '.')])
|
qid, err = strconv.Atoi((filename)[strings.LastIndexByte(filename, '/')+1 : strings.LastIndexByte(filename, '.')])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := os.Open(*filename)
|
h, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,7 +77,9 @@ func Message(filename *string) (from string, rcpts []string, auth string, ip str
|
|||||||
var line []byte
|
var line []byte
|
||||||
var pos int64
|
var pos int64
|
||||||
|
|
||||||
for m := bufio.NewReader(h); ; {
|
m := bufio.NewReader(h)
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
line, err = m.ReadSlice('\n')
|
line, err = m.ReadSlice('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,7 +112,14 @@ func Message(filename *string) (from string, rcpts []string, auth string, ip str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rcpts = uniqueNonEmptyElementsOf(rcpts)
|
seen, err = isSeen(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if seen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fi, err := h.Stat()
|
fi, err := h.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -116,6 +137,8 @@ func Message(filename *string) (from string, rcpts []string, auth string, ip str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rcpts = uniqueNonEmptyElementsOf(rcpts)
|
||||||
|
|
||||||
if from == "" || len(rcpts) == 0 || n < len(body) {
|
if from == "" || len(rcpts) == 0 || n < len(body) {
|
||||||
err = fmt.Errorf("cgp.Message() error: from='%s', len(to)=%d, auth='%s' ip='%s', size=%d/%d", from, len(rcpts), auth, ip, len(body), n)
|
err = fmt.Errorf("cgp.Message() error: from='%s', len(to)=%d, auth='%s' ip='%s', size=%d/%d", from, len(rcpts), auth, ip, len(body), n)
|
||||||
}
|
}
|
||||||
@@ -123,10 +146,43 @@ func Message(filename *string) (from string, rcpts []string, auth string, ip str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MirrorTo(seq int, to []string, headers []string, mirrorDiscard bool) {
|
||||||
|
|
||||||
|
hdrs := replaceSpecChars(strings.Join(headers, "\n"))
|
||||||
|
|
||||||
|
if protocol >= 4 {
|
||||||
|
|
||||||
|
if len(to) > 0 {
|
||||||
|
|
||||||
|
mirrorTo := []string{}
|
||||||
|
for _, m := range to {
|
||||||
|
mirrorTo = append(mirrorTo, fmt.Sprintf("MIRRORTO \"%s\"", m))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mirrorDiscard {
|
||||||
|
Putline("%d %s DISCARD\n", seq, strings.Join(mirrorTo, " "))
|
||||||
|
} else {
|
||||||
|
Putline("%d %s ADDHEADER \"%s\" OK\n", seq, strings.Join(mirrorTo, " "), hdrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Putline("%d ADDHEADER \"%s\" OK\n", seq, hdrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Putline("%d ADDHEADER \"%s\"\n", seq, hdrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Ok(seq int) {
|
func Ok(seq int) {
|
||||||
Putline("%d OK\n", seq)
|
Putline("%d OK\n", seq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OkSeen(seq, qid int) {
|
||||||
|
Putline("* %d [%d]: Already seen by Rspamd\n", seq, qid)
|
||||||
|
Putline("%d OK\n", seq)
|
||||||
|
}
|
||||||
|
|
||||||
func Putline(format string, a ...interface{}) {
|
func Putline(format string, a ...interface{}) {
|
||||||
s := fmt.Sprintf(format, a...)
|
s := fmt.Sprintf(format, a...)
|
||||||
syscall.Write(int(os.Stdout.Fd()), []byte(s))
|
syscall.Write(int(os.Stdout.Fd()), []byte(s))
|
||||||
@@ -136,16 +192,274 @@ func Reject(seq int) {
|
|||||||
Putline("%d REJECT Try again later\n", seq)
|
Putline("%d REJECT Try again later\n", seq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceSpecChars(msgo *string) *string {
|
func RewriteSubject(seq int, headers []string, subject string, qid int, from string, rcpts []string, body []byte) {
|
||||||
|
|
||||||
var msgn string
|
var err error
|
||||||
|
var firstRecv bool = true
|
||||||
|
var line []byte
|
||||||
|
var m *bufio.Reader
|
||||||
|
var hdr string
|
||||||
|
|
||||||
for _, symbol := range *msgo {
|
filename := fmt.Sprintf("%s/%drs.sub", submitDir, qid)
|
||||||
|
filetemp := strings.Replace(filename, "sub", "tmp", 1)
|
||||||
|
|
||||||
|
fh, err := os.Create(filetemp)
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
_, err = fh.WriteString(strings.Join([]string{"Return-Path: ", from, "\n"}, ""))
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rcpt := range rcpts {
|
||||||
|
_, err = fh.WriteString(strings.Join([]string{"Envelope-To: ", rcpt, "\n"}, ""))
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m = bufio.NewReader(bytes.NewReader(body))
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
hdr, err = getHeader(m)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
if len(hdr) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr == "\n" {
|
||||||
|
// конец заголовка
|
||||||
|
_, err = fh.WriteString(hdr)
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstRecv && strings.HasPrefix(hdr, recvHdr) {
|
||||||
|
|
||||||
|
sum := fmt.Sprintf("%x", sha256.Sum224([]byte(nospace(hdr))))
|
||||||
|
|
||||||
|
_, err = fh.WriteString(strings.Join([]string{seenHdr, " ", sum, "\n"}, ""))
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fh.WriteString(hdr)
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRecv = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(hdr, subjHdr) {
|
||||||
|
|
||||||
|
_, err = fh.WriteString(strings.Join([]string{subjHdr, " ", subject, "\n"}, ""))
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fh.WriteString(hdr)
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err = m.ReadSlice('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fh.Write(line)
|
||||||
|
if err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fh.Close(); err != nil {
|
||||||
|
goto fin
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Rename(filetemp, filename)
|
||||||
|
|
||||||
|
fin:
|
||||||
|
if err != nil {
|
||||||
|
Failure(seq, qid, err)
|
||||||
|
} else {
|
||||||
|
Discard(seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeader(m *bufio.Reader) (hdr string, err error) {
|
||||||
|
|
||||||
|
var c byte
|
||||||
|
var line []byte
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.Grow(384)
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err = m.ReadByte()
|
||||||
|
if err == io.EOF {
|
||||||
|
if c == 0 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Len() == 0 {
|
||||||
|
|
||||||
|
if c == ' ' || c == '\t' {
|
||||||
|
err = m.UnreadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("bad header")
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if c == '\n' {
|
||||||
|
|
||||||
|
b.WriteByte(c)
|
||||||
|
break
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
b.WriteByte(c)
|
||||||
|
|
||||||
|
line, err = m.ReadSlice('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Write(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if c == ' ' || c == '\t' {
|
||||||
|
|
||||||
|
b.WriteByte(c)
|
||||||
|
|
||||||
|
line, err = m.ReadSlice('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Write(line)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = m.UnreadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr = b.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSeen(m *bufio.Reader) (seen bool, err error) {
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
var seenSum string
|
||||||
|
var hdr string
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
hdr, err = getHeader(m)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
if len(hdr) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hdr == "\n" {
|
||||||
|
// конец заголовка
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
|
||||||
|
if seenSum, found = strings.CutPrefix(hdr, seenHdr); found {
|
||||||
|
seenSum = strings.TrimSpace(seenSum)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if strings.HasPrefix(hdr, recvHdr) {
|
||||||
|
|
||||||
|
sum := fmt.Sprintf("%x", sha256.Sum224([]byte(nospace(hdr))))
|
||||||
|
if seenSum == sum {
|
||||||
|
seen = true
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func nospace(s string) string {
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.Grow(len(s))
|
||||||
|
|
||||||
|
for _, c := range s {
|
||||||
|
if !unicode.IsSpace(c) {
|
||||||
|
b.WriteRune(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceSpecChars(msg string) string {
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.Grow(len(msg) + 128)
|
||||||
|
|
||||||
|
for _, symbol := range msg {
|
||||||
|
|
||||||
switch symbol {
|
switch symbol {
|
||||||
case rune('\\'):
|
case rune('\\'):
|
||||||
// replace \ -> \\ (CGP backslash)
|
// replace \ -> \\ (CGP backslash)
|
||||||
msgn += "\\\\"
|
sb.WriteString("\\\\")
|
||||||
|
|
||||||
case 0x0000:
|
case 0x0000:
|
||||||
fallthrough
|
fallthrough
|
||||||
@@ -154,22 +468,22 @@ func ReplaceSpecChars(msgo *string) *string {
|
|||||||
|
|
||||||
case rune('\n'):
|
case rune('\n'):
|
||||||
// replace \n -> \\e (CGP End-of-Line)
|
// replace \n -> \\e (CGP End-of-Line)
|
||||||
msgn += "\\e"
|
sb.WriteString("\\e")
|
||||||
|
|
||||||
case rune('\t'):
|
case rune('\t'):
|
||||||
// replace \t -> \\t (CGP Tab)
|
// replace \t -> \\t (CGP Tab)
|
||||||
msgn += "\\t"
|
sb.WriteString("\\t")
|
||||||
|
|
||||||
case rune('"'):
|
case rune('"'):
|
||||||
// replace \" -> \\" (CGP quote)
|
// replace \" -> \\" (CGP quote)
|
||||||
msgn += "\\\""
|
sb.WriteString("\\\"")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
msgn += string(symbol)
|
sb.WriteRune(symbol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &msgn
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setMainDomain() (err error) {
|
func setMainDomain() (err error) {
|
||||||
|
|||||||
+17
-8
@@ -2,31 +2,40 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AuthservId string
|
AuthservId string
|
||||||
Discard bool
|
Debug bool
|
||||||
Host string
|
Host string
|
||||||
Timeout time.Duration
|
MirrorDiscard bool
|
||||||
|
MirrorTo []string
|
||||||
|
Timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Config {
|
func New() *Config {
|
||||||
|
|
||||||
config := new(Config)
|
config := new(Config)
|
||||||
|
|
||||||
var rejectAction string
|
var mirrorTo string
|
||||||
|
|
||||||
flag.StringVar(&config.AuthservId, "authserv-id", "", "Authentication Identifier (default CommuniGate Pro Main Domain)")
|
flag.StringVar(&config.AuthservId, "authserv-id", "", "Authentication Identifier (default CommuniGate Pro Main Domain)")
|
||||||
flag.StringVar(&config.Host, "host", "localhost:11333", "Rspamd host to connect")
|
flag.StringVar(&config.Host, "host", "localhost:11333", "Rspamd host to connect")
|
||||||
flag.StringVar(&rejectAction, "reject-action", "add_header", "Reject action: \"add_header\" or \"discard\"")
|
flag.BoolVar(&config.MirrorDiscard, "mirror-discard", false, "Mirror then discard selected messages")
|
||||||
|
flag.StringVar(&mirrorTo, "mirror-to", "", "Mirror selected messages to email")
|
||||||
flag.DurationVar(&config.Timeout, "timeout", 15*time.Second, "Rspamd request timeout")
|
flag.DurationVar(&config.Timeout, "timeout", 15*time.Second, "Rspamd request timeout")
|
||||||
|
flag.BoolVar(&config.Debug, "debug", false, "Export debug information (for developers)")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if rejectAction == "discard" {
|
if len(mirrorTo) > 0 {
|
||||||
config.Discard = true
|
config.MirrorTo = strings.Split(strings.ReplaceAll(mirrorTo, " ", ""), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.MirrorDiscard && len(config.MirrorTo) == 0 {
|
||||||
|
config.MirrorDiscard = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Timeout < time.Second {
|
if config.Timeout < time.Second {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
module git.vsu.ru/ai/rspamd-cgp
|
module git.vsu.ru/ai/rspamd-cgp
|
||||||
|
|
||||||
go 1.16
|
go 1.18
|
||||||
|
|
||||||
require github.com/json-iterator/go v1.1.10
|
require github.com/json-iterator/go v1.1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
|||||||
+55
-38
@@ -5,7 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
json "github.com/json-iterator/go"
|
json "github.com/json-iterator/go"
|
||||||
|
|
||||||
@@ -21,8 +24,10 @@ var (
|
|||||||
|
|
||||||
var authservId string
|
var authservId string
|
||||||
var client *http.Client
|
var client *http.Client
|
||||||
var discard bool
|
var mirrorDiscard bool
|
||||||
|
var mirrorTo []string
|
||||||
var host string
|
var host string
|
||||||
|
var debug bool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
@@ -34,8 +39,10 @@ func init() {
|
|||||||
authservId = cgp.MainDomain
|
authservId = cgp.MainDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
discard = config.Discard
|
mirrorDiscard = config.MirrorDiscard
|
||||||
|
mirrorTo = config.MirrorTo
|
||||||
host = "http://" + config.Host + "/checkv2"
|
host = "http://" + config.Host + "/checkv2"
|
||||||
|
debug = config.Debug
|
||||||
|
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
DisableCompression: true,
|
DisableCompression: true,
|
||||||
@@ -47,23 +54,24 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendHeaders(hdrs, junk *string) *string {
|
func printResponse(v any) {
|
||||||
if *hdrs != "" {
|
printed, _ := json.MarshalIndent(v, "", " ")
|
||||||
headers := *hdrs + "\n" + *junk
|
fmt.Fprintln(os.Stderr, string(printed))
|
||||||
return &headers
|
|
||||||
} else {
|
|
||||||
return junk
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Scan(seq int, filename string) {
|
func Scan(seq int, filename string) {
|
||||||
|
|
||||||
from, rcpts, auth, ip, qid, body, err := cgp.Message(&filename)
|
from, rcpts, auth, ip, qid, body, seen, err := cgp.Message(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cgp.Failure(seq, qid, err)
|
cgp.Failure(seq, qid, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if seen {
|
||||||
|
cgp.OkSeen(seq, qid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", host, bytes.NewReader(body))
|
req, err := http.NewRequest("POST", host, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cgp.Failure(seq, qid, err)
|
cgp.Failure(seq, qid, err)
|
||||||
@@ -97,65 +105,74 @@ func Scan(seq int, filename string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var js map[string]interface{}
|
var res map[string]interface{}
|
||||||
if err := json.Unmarshal(rbody, &js); err != nil {
|
if err := json.Unmarshal(rbody, &res); err != nil {
|
||||||
cgp.Failure(seq, qid, err)
|
cgp.Failure(seq, qid, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var headers string
|
if debug {
|
||||||
|
printResponse(res)
|
||||||
if _, ok := js["dkim-signature"]; ok {
|
|
||||||
headers = "DKIM-Signature: " + js["dkim-signature"].(string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if milter, ok := js["milter"]; ok {
|
var headers []string
|
||||||
|
|
||||||
|
if _, ok := res["dkim-signature"]; ok {
|
||||||
|
headers = append(headers, strings.Join([]string{"DKIM-Signature: ", res["dkim-signature"].(string)}, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if milter, ok := res["milter"]; ok {
|
||||||
if hdrs, ok := milter.(map[string]interface{})["add_headers"]; ok {
|
if hdrs, ok := milter.(map[string]interface{})["add_headers"]; ok {
|
||||||
for h, vh := range hdrs.(map[string]interface{}) {
|
if reflect.TypeOf(hdrs).String() == "map[string]interface {}" {
|
||||||
if v, ok := vh.(map[string]interface{})["value"].(string); ok && v != "" {
|
for h, vh := range hdrs.(map[string]interface{}) {
|
||||||
if headers != "" {
|
if reflect.TypeOf(vh).String() == "map[string]interface {}" {
|
||||||
headers += "\n" + h + ": " + v
|
if v, ok := vh.(map[string]interface{})["value"].(string); ok && v != "" {
|
||||||
} else {
|
headers = append(headers, strings.Join([]string{h, v}, ": "))
|
||||||
headers = h + ": " + v
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
action := js["action"]
|
action := res["action"]
|
||||||
|
|
||||||
cgp.Putline("* %d [%d]: Action: %s; Score: %.2f/%.2f; Time elapsed: %.3fs\n",
|
cgp.Putline("* %d [%d]: Action: %s; Score: %.2f/%.2f; Time elapsed: %.3fs\n",
|
||||||
seq, qid, action, js["score"], js["required_score"], js["time_real"])
|
seq, qid, action, res["score"], res["required_score"], res["time_real"])
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case "no action":
|
case "no action":
|
||||||
if headers != "" {
|
if len(headers) > 0 {
|
||||||
cgp.AddHeader(seq, cgp.ReplaceSpecChars(&headers))
|
cgp.AddHeader(seq, headers)
|
||||||
} else {
|
} else {
|
||||||
cgp.Ok(seq)
|
cgp.Ok(seq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "discard":
|
||||||
|
cgp.Discard(seq)
|
||||||
|
|
||||||
|
case "quarantine":
|
||||||
|
cgp.MirrorTo(seq, mirrorTo, append(headers, headerJunkR), mirrorDiscard)
|
||||||
|
|
||||||
case "reject":
|
case "reject":
|
||||||
if discard {
|
cgp.AddHeader(seq, append(headers, headerJunkR))
|
||||||
cgp.Discard(seq)
|
|
||||||
} else {
|
|
||||||
cgp.AddHeader(seq, cgp.ReplaceSpecChars(appendHeaders(&headers, &headerJunkR)))
|
|
||||||
}
|
|
||||||
|
|
||||||
case "rewrite subject":
|
case "rewrite subject":
|
||||||
fallthrough
|
if subject, ok := res["subject"]; ok {
|
||||||
|
cgp.RewriteSubject(seq, append(headers, headerJunkA), subject.(string), qid, from, rcpts, body)
|
||||||
|
} else {
|
||||||
|
cgp.AddHeader(seq, append(headers, headerJunkA))
|
||||||
|
}
|
||||||
|
|
||||||
case "add header":
|
case "add header":
|
||||||
cgp.AddHeader(seq, cgp.ReplaceSpecChars(appendHeaders(&headers, &headerJunkA)))
|
cgp.AddHeader(seq, append(headers, headerJunkA))
|
||||||
|
|
||||||
case "greylist":
|
case "greylist":
|
||||||
cgp.AddHeader(seq, cgp.ReplaceSpecChars(appendHeaders(&headers, &headerJunkG)))
|
fallthrough
|
||||||
|
|
||||||
case "soft reject":
|
case "soft reject":
|
||||||
cgp.Reject(seq)
|
cgp.AddHeader(seq, append(headers, headerJunkG))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
cgp.Failure(seq, qid, fmt.Errorf("Unknown action: %v", action))
|
cgp.Failure(seq, qid, fmt.Errorf("Unknown action: %v", action))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-7
@@ -1,7 +0,0 @@
|
|||||||
# github.com/json-iterator/go v1.1.10
|
|
||||||
## explicit
|
|
||||||
github.com/json-iterator/go
|
|
||||||
# github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
|
|
||||||
github.com/modern-go/concurrent
|
|
||||||
# github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
|
|
||||||
github.com/modern-go/reflect2
|
|
||||||
Reference in New Issue
Block a user