/
nginx.go
132 lines (113 loc) · 3.64 KB
/
nginx.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
package proxy
import (
"fmt"
"github.com/Polymail/go-falcon/config"
"github.com/Polymail/go-falcon/log"
"github.com/Polymail/go-falcon/utils"
"net/http"
"strconv"
"strings"
"sync"
)
const (
MAX_AUTH_RETRY = 10
INVALID_AUTH_WAIT_TIME = "3"
PROTOCOL_SMTP = "smtp"
PROTOCOL_POP3 = "pop3"
)
var (
roundRobinIterator = 0
roundRobinMutex = &sync.Mutex{}
)
// If running Nginx as a proxy, give Nginx the IP address and port for the SMTP server
// Primary use of Nginx is to terminate TLS so that Go doesn't need to deal with it.
// This could perform auth and load balancing too
// See http://wiki.nginx.org/MailCoreModule
func StartNginxHTTPProxy(config *config.Config) {
if config.Proxy.Enabled {
go nginxHTTPAuth(config)
}
}
// nginx auth server
func nginxHTTPAuth(config *config.Config) {
// handle
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
nginxHTTPAuthHandler(w, r, config)
})
// server ip:port
serverBind := fmt.Sprintf("%s:%d", config.Proxy.Host, config.Proxy.Port)
//
log.Debugf("Nginx proxy working on %s", serverBind)
//
errServer := http.ListenAndServe(serverBind, nil)
if errServer != nil {
log.Errorf("Nginx proxy: %v", errServer)
}
}
// nginx auth by nginx headers
func nginxHTTPAuthHandler(w http.ResponseWriter, r *http.Request, config *config.Config) {
log.Debugf("Nginx proxy get request: %v", r)
protocol := strings.ToLower(r.Header.Get("Auth-Protocol"))
if protocol == PROTOCOL_SMTP || protocol == PROTOCOL_POP3 {
if config.Adapter.Auth || config.Pop3.Enabled {
authMethod := strings.ToLower(r.Header.Get("Auth-Method"))
username := r.Header.Get("Auth-User")
password := r.Header.Get("Auth-Pass")
secret := ""
if authMethod == utils.AUTH_CRAM_MD5 || authMethod == utils.AUTH_APOP {
secret = r.Header.Get("Auth-Salt")
}
id, pass, err := config.DbPool.CheckUserWithPass(authMethod, username, password, secret)
if err != nil {
nginxResponseFail(w, r)
return
}
nginxResponseSuccess(config, w, protocol, strconv.Itoa(id), pass)
} else {
nginxResponseSuccess(config, w, protocol, "0", "")
}
} else {
nginxResponseFail(w, r)
}
}
// success auth response
func nginxResponseSuccess(config *config.Config, w http.ResponseWriter, protocol, userId, password string) {
serverHost, serverPort := config.Adapter.Host, strconv.Itoa(getRoundRobinFromArray(config.SmtpPortRanges))
if protocol == PROTOCOL_SMTP {
w.Header().Add("Auth-User", userId) // return mailbox id instead username
} else if protocol == PROTOCOL_POP3 {
serverHost, serverPort = config.Pop3.Host, strconv.Itoa(utils.GetRandFromArray(config.Pop3PortRanges)) // revrite server options
w.Header().Add("Auth-Pass", password) // return password for pop3
}
w.Header().Add("Auth-Status", "OK")
w.Header().Add("Auth-Server", serverHost)
w.Header().Add("Auth-Port", serverPort)
// empty body
fmt.Fprint(w, "")
}
// fail auth response
func nginxResponseFail(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Auth-Status", "Invalid login or password")
// login attempt
loginAttempt := r.Header.Get("Auth-Login-Attempt")
if loginAttempt != "" {
loginAttemptInt, err := strconv.Atoi(loginAttempt)
if err == nil {
if loginAttemptInt < MAX_AUTH_RETRY {
w.Header().Add("Auth-Wait", INVALID_AUTH_WAIT_TIME)
}
}
}
// empty body
fmt.Fprint(w, "")
}
// round robin
func getRoundRobinFromArray(arr []int) int {
roundRobinMutex.Lock()
defer roundRobinMutex.Unlock()
roundRobinIterator = roundRobinIterator + 1
if roundRobinIterator >= len(arr) {
roundRobinIterator = 0
}
return arr[roundRobinIterator]
}