-
Notifications
You must be signed in to change notification settings - Fork 13
/
spamassassin.go
148 lines (126 loc) · 3.88 KB
/
spamassassin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// ClueGetter - Does things with mail
//
// Copyright 2016 Dolf Schimmel, Freeaqingme.
//
// This Source Code Form is subject to the terms of the two-clause BSD license.
// For its contents, please refer to the LICENSE file.
//
package core
import (
"fmt"
spamc "github.com/Freeaqingme/go-spamc"
"strconv"
"strings"
)
type saReport struct {
score float64
facts []*saReportFact
}
type saReportFact struct {
Score float64
Symbol string
Description string
}
func init() {
enable := func() bool { return Config.SpamAssassin.Enabled }
milterCheck := saGetResult
ModuleRegister(&ModuleOld{
name: "spamassassin",
enable: &enable,
milterCheck: &milterCheck,
})
}
func saGetResult(msg *Message, abort chan bool) *MessageCheckResult {
if !msg.session.config.SpamAssassin.Enabled {
return nil
}
rawReply, err := saGetRawReply(msg, abort)
if err != nil || rawReply.Code != spamc.EX_OK {
Log.Error("SpamAssassin returned an error: %s", err)
return &MessageCheckResult{
Module: "spamassassin",
SuggestedAction: MessageError,
Message: "An internal error occurred",
Score: 25,
Determinants: map[string]interface{}{"error": err.Error()},
}
}
Log.Debug("Getting SA report for %s", msg.QueueId)
report := saParseReply(rawReply)
factsStr := func() []string {
out := make([]string, 0)
for _, fact := range report.facts {
out = append(out, fmt.Sprintf("%s=%.3f", fact.Symbol, fact.Score))
}
return out
}()
Log.Debug("Got SA score of %.2f for %s. Tests: [%s]", report.score, msg.QueueId, strings.Join(factsStr, ","))
return &MessageCheckResult{
Module: "spamassassin",
SuggestedAction: MessageReject,
Message: "Our system has detected that this message is likely unsolicited mail (SPAM). " +
"To reduce the amount of spam, this message has been blocked.",
Score: report.score,
Determinants: map[string]interface{}{"report": report.facts},
}
}
func saGetRawReply(msg *Message, abort chan bool) (*spamc.SpamDOut, error) {
bodyStr := string(msg.String())
host := Config.SpamAssassin.Host + ":" + strconv.Itoa(Config.SpamAssassin.Port)
sconf := msg.session.config.SpamAssassin
client := spamc.New(host, sconf.Timeout, sconf.Connect_Timeout)
if len(bodyStr) > sconf.Max_Size {
bodyStr = bodyStr[:sconf.Max_Size]
}
return client.Report(abort, bodyStr, msg.Rcpt[0].String())
}
/*
The spamc client library returns a pretty shitty
format, So we try to make the best of it and
parse it into some nice structs.
*/
func saParseReply(reply *spamc.SpamDOut) *saReport {
report := &saReport{facts: make([]*saReportFact, 0)}
for key, value := range reply.Vars {
if key == "spamScore" {
report.score = value.(float64)
} else if key == "report" {
var reportFacts []map[string]interface{}
reportFacts = value.([]map[string]interface{})
for _, reportFact := range reportFacts {
report.facts = append(report.facts, saParseReplyReportVar(reportFact))
}
}
}
return report
}
func saParseReplyReportVar(reportFactRaw map[string]interface{}) *saReportFact {
reportFact := &saReportFact{}
for key, value := range reportFactRaw {
switch {
case key == "score":
reportFact.Score = value.(float64)
case key == "symbol":
reportFact.Symbol = value.(string)
case key == "message":
reportFact.Description = value.(string)
}
}
return reportFact
}
func saLearn(msg *Proto_Message, spam bool) {
if !Config.SpamAssassin.Enabled {
return
}
bodyStr := string(bayesRenderProtoMsg(msg))
host := Config.SpamAssassin.Host + ":" + strconv.Itoa(Config.SpamAssassin.Port)
sconf := Config.SpamAssassin
client := spamc.New(host, sconf.Timeout, sconf.Connect_Timeout)
abort := make(chan bool) // unused really. To implement or not to implement. That's the question
if spam {
client.Learn(abort, spamc.LEARN_SPAM, bodyStr)
} else {
client.Learn(abort, spamc.LEARN_HAM, bodyStr)
}
close(abort)
}