-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathmain.go
More file actions
136 lines (123 loc) · 2.83 KB
/
main.go
File metadata and controls
136 lines (123 loc) · 2.83 KB
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
package main
import (
"bufio"
"encoding/csv"
"fmt"
"io"
"os"
"regexp"
"strings"
)
const mask = "xxxx"
func main() {
// skip utf-8 byte order mark if present
br := bufio.NewReader(os.Stdin)
bom, err := br.Peek(3)
if err != nil {
return
}
if bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF {
br.Discard(3)
}
r := csv.NewReader(br)
r.ReuseRecord = true
// read header line
header, err := r.Read()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
if len(header) == 0 || header[0] != "type" {
fmt.Fprintf(os.Stderr, "Expected header with \"type,...\"\n")
os.Exit(1)
}
// find index of rcpt and dsnDiag
var (
origIndex int
rcptIndex int
dsnDiagIndex int
)
for i, field := range header {
switch field {
case "orig":
origIndex = i
case "rcpt":
rcptIndex = i
case "dsnDiag":
dsnDiagIndex = i
}
}
w := csv.NewWriter(os.Stdout)
err = w.Write(header)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
for {
// read next record
record, err := r.Read()
if err != nil {
if err == io.EOF {
break
}
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
if rcptIndex != 0 {
rcpt := record[rcptIndex]
if rcpt != "" { // empty for tq records
user, domain := splitEmail(rcpt)
if user != "" {
// mask local part in rcpt
record[rcptIndex] = mask + "@" + domain
// mask user in VERP address (e.g. jsmith-jdoe=yahoo.com@example.com)
if origIndex != 0 {
record[origIndex] = maskInVERP(record[origIndex], user)
}
// mask user in dsnDiag
if dsnDiagIndex != 0 {
record[dsnDiagIndex] = maskInDSN(record[dsnDiagIndex], user)
}
}
}
}
err = w.Write(record)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
w.Flush()
}
// split email address in local and domain part
func splitEmail(email string) (user, domain string) {
if i := strings.IndexRune(email, '@'); i != -1 {
user = email[0:i]
domain = strings.ToLower(email[i+1:])
} else {
domain = strings.ToLower(email) // no local part
}
return
}
// mask user in orig, e.g. jsmith-jdoe=yahoo.com@example.com
func maskInVERP(orig string, user string) string {
if i := strings.Index(orig, user); i != -1 {
if i+len(user) < len(orig) && orig[i+len(user)] == '=' {
return orig[:i] + mask + orig[i+len(user):]
}
}
return orig
}
// mask user in delivery status notification
func maskInDSN(dsn string, user string) string {
// do quick find first, could match part of regular word (e.g. pien in recipient)
if strings.Index(dsn, user) != -1 {
// replace user when enclosed in word boundaries
re, err := regexp.Compile(`\b` + regexp.QuoteMeta(user) + `\b`)
if err != nil {
panic(fmt.Sprintf("Error compiling regexp \\b%s\\b: %v", user, err))
}
return re.ReplaceAllString(dsn, mask)
}
return dsn
}