Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
N
Ntfy_archive
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
TeDomum
Ntfy_archive
Commits
e7f8fc93
Commit
e7f8fc93
authored
3 years ago
by
Philipp Heckel
Browse files
Options
Downloads
Patches
Plain Diff
Working prefix
parent
7eaa92cb
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
server/server.go
+37
-27
37 additions, 27 deletions
server/server.go
server/smtp_server.go
+87
-53
87 additions, 53 deletions
server/smtp_server.go
with
124 additions
and
80 deletions
server/server.go
+
37
−
27
View file @
e7f8fc93
...
...
@@ -33,6 +33,8 @@ type Server struct {
config
*
Config
httpServer
*
http
.
Server
httpsServer
*
http
.
Server
smtpServer
*
smtp
.
Server
smtpBackend
*
smtpBackend
topics
map
[
string
]
*
topic
visitors
map
[
string
]
*
visitor
firebase
subscriber
...
...
@@ -85,11 +87,12 @@ var (
)
var
(
topicRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}$`
)
// Regex must match JS & Android app!
jsonRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`
)
sseRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`
)
rawRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`
)
sendRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`
)
topicRegex
=
regexp
.
MustCompile
(
`^[-_A-Za-z0-9]{1,64}$`
)
// No /!
topicPathRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}$`
)
// Regex must match JS & Android app!
jsonPathRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/json$`
)
ssePathRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/sse$`
)
rawPathRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/raw$`
)
publishPathRegex
=
regexp
.
MustCompile
(
`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/(publish|send|trigger)$`
)
staticRegex
=
regexp
.
MustCompile
(
`^/static/.+`
)
docsRegex
=
regexp
.
MustCompile
(
`^/docs(|/.*)$`
)
...
...
@@ -225,6 +228,9 @@ func (s *Server) Run() error {
if
s
.
config
.
ListenHTTPS
!=
""
{
listenStr
+=
fmt
.
Sprintf
(
" %s/https"
,
s
.
config
.
ListenHTTPS
)
}
if
s
.
config
.
SMTPServerListen
!=
""
{
listenStr
+=
fmt
.
Sprintf
(
" %s/smtp"
,
s
.
config
.
SMTPServerListen
)
}
log
.
Printf
(
"Listening on %s"
,
listenStr
)
mux
:=
http
.
NewServeMux
()
mux
.
HandleFunc
(
"/"
,
s
.
handle
)
...
...
@@ -243,7 +249,7 @@ func (s *Server) Run() error {
}
if
s
.
config
.
SMTPServerListen
!=
""
{
go
func
()
{
errChan
<-
s
.
run
Mails
erver
()
errChan
<-
s
.
run
SMTPS
erver
()
}()
}
s
.
mu
.
Unlock
()
...
...
@@ -264,6 +270,9 @@ func (s *Server) Stop() {
if
s
.
httpsServer
!=
nil
{
s
.
httpsServer
.
Close
()
}
if
s
.
smtpServer
!=
nil
{
s
.
smtpServer
.
Close
()
}
close
(
s
.
closeChan
)
}
...
...
@@ -295,17 +304,17 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request) error {
return
s
.
handleDocs
(
w
,
r
)
}
else
if
r
.
Method
==
http
.
MethodOptions
{
return
s
.
handleOptions
(
w
,
r
)
}
else
if
r
.
Method
==
http
.
MethodGet
&&
topicRegex
.
MatchString
(
r
.
URL
.
Path
)
{
}
else
if
r
.
Method
==
http
.
MethodGet
&&
topic
Path
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
return
s
.
handleTopic
(
w
,
r
)
}
else
if
(
r
.
Method
==
http
.
MethodPut
||
r
.
Method
==
http
.
MethodPost
)
&&
topicRegex
.
MatchString
(
r
.
URL
.
Path
)
{
}
else
if
(
r
.
Method
==
http
.
MethodPut
||
r
.
Method
==
http
.
MethodPost
)
&&
topic
Path
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
return
s
.
withRateLimit
(
w
,
r
,
s
.
handlePublish
)
}
else
if
r
.
Method
==
http
.
MethodGet
&&
send
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
}
else
if
r
.
Method
==
http
.
MethodGet
&&
publishPath
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
return
s
.
withRateLimit
(
w
,
r
,
s
.
handlePublish
)
}
else
if
r
.
Method
==
http
.
MethodGet
&&
jsonRegex
.
MatchString
(
r
.
URL
.
Path
)
{
}
else
if
r
.
Method
==
http
.
MethodGet
&&
json
Path
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
return
s
.
withRateLimit
(
w
,
r
,
s
.
handleSubscribeJSON
)
}
else
if
r
.
Method
==
http
.
MethodGet
&&
sseRegex
.
MatchString
(
r
.
URL
.
Path
)
{
}
else
if
r
.
Method
==
http
.
MethodGet
&&
sse
Path
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
return
s
.
withRateLimit
(
w
,
r
,
s
.
handleSubscribeSSE
)
}
else
if
r
.
Method
==
http
.
MethodGet
&&
rawRegex
.
MatchString
(
r
.
URL
.
Path
)
{
}
else
if
r
.
Method
==
http
.
MethodGet
&&
raw
Path
Regex
.
MatchString
(
r
.
URL
.
Path
)
{
return
s
.
withRateLimit
(
w
,
r
,
s
.
handleSubscribeRaw
)
}
return
errHTTPNotFound
...
...
@@ -726,12 +735,15 @@ func (s *Server) updateStatsAndPrune() {
messages
+=
msgs
}
// Mail
mailSuccess
,
mailFailure
:=
s
.
smtpBackend
.
Counts
()
// Print stats
log
.
Printf
(
"Stats: %d message(s) published, %d topic(s) active, %d subscriber(s), %d
message(s) buffered, %d
visitor(s)"
,
s
.
messages
,
len
(
s
.
topics
),
subscribers
,
messages
,
len
(
s
.
visitors
))
log
.
Printf
(
"Stats: %d message(s) published, %d
in cache, %d successful mails, %d failed, %d
topic(s) active, %d subscriber(s), %d visitor(s)"
,
s
.
messages
,
messages
,
mailSuccess
,
mailFailure
,
len
(
s
.
topics
),
subscribers
,
len
(
s
.
visitors
))
}
func
(
s
*
Server
)
run
Mails
erver
()
error
{
func
(
s
*
Server
)
run
SMTPS
erver
()
error
{
sub
:=
func
(
m
*
message
)
error
{
url
:=
fmt
.
Sprintf
(
"%s/%s"
,
s
.
config
.
BaseURL
,
m
.
Topic
)
req
,
err
:=
http
.
NewRequest
(
"PUT"
,
url
,
strings
.
NewReader
(
m
.
Message
))
...
...
@@ -748,18 +760,16 @@ func (s *Server) runMailserver() error {
}
return
nil
}
ms
:=
smtp
.
NewServer
(
newMailBackend
(
s
.
config
,
sub
))
ms
.
Addr
=
s
.
config
.
SMTPServerListen
ms
.
Domain
=
s
.
config
.
SMTPServerDomain
ms
.
ReadTimeout
=
10
*
time
.
Second
ms
.
WriteTimeout
=
10
*
time
.
Second
ms
.
MaxMessageBytes
=
2
*
s
.
config
.
MessageLimit
ms
.
MaxRecipients
=
1
ms
.
AllowInsecureAuth
=
true
log
.
Println
(
"Starting server at"
,
ms
.
Addr
)
return
ms
.
ListenAndServe
()
s
.
smtpBackend
=
newMailBackend
(
s
.
config
,
sub
)
s
.
smtpServer
=
smtp
.
NewServer
(
s
.
smtpBackend
)
s
.
smtpServer
.
Addr
=
s
.
config
.
SMTPServerListen
s
.
smtpServer
.
Domain
=
s
.
config
.
SMTPServerDomain
s
.
smtpServer
.
ReadTimeout
=
10
*
time
.
Second
s
.
smtpServer
.
WriteTimeout
=
10
*
time
.
Second
s
.
smtpServer
.
MaxMessageBytes
=
2
*
s
.
config
.
MessageLimit
s
.
smtpServer
.
MaxRecipients
=
1
s
.
smtpServer
.
AllowInsecureAuth
=
true
return
s
.
smtpServer
.
ListenAndServe
()
}
func
(
s
*
Server
)
runManager
()
{
...
...
This diff is collapsed.
Click to expand it.
server/smtp_server.go
+
87
−
53
View file @
e7f8fc93
...
...
@@ -5,17 +5,25 @@ import (
"errors"
"github.com/emersion/go-smtp"
"io"
"io/ioutil"
"log"
"net/mail"
"strings"
"sync"
)
var
(
errInvalidDomain
=
errors
.
New
(
"invalid domain"
)
errInvalidAddress
=
errors
.
New
(
"invalid address"
)
errInvalidTopic
=
errors
.
New
(
"invalid topic"
)
errTooManyRecipients
=
errors
.
New
(
"too many recipients"
)
)
// smtpBackend implements SMTP server methods.
type
smtpBackend
struct
{
config
*
Config
sub
subscriber
config
*
Config
sub
subscriber
success
int64
failure
int64
mu
sync
.
Mutex
}
func
newMailBackend
(
conf
*
Config
,
sub
subscriber
)
*
smtpBackend
{
...
...
@@ -26,19 +34,24 @@ func newMailBackend(conf *Config, sub subscriber) *smtpBackend {
}
func
(
b
*
smtpBackend
)
Login
(
state
*
smtp
.
ConnectionState
,
username
,
password
string
)
(
smtp
.
Session
,
error
)
{
return
&
smtpSession
{
config
:
b
.
config
,
sub
:
b
.
su
b
},
nil
return
&
smtpSession
{
backend
:
b
},
nil
}
func
(
b
*
smtpBackend
)
AnonymousLogin
(
state
*
smtp
.
ConnectionState
)
(
smtp
.
Session
,
error
)
{
return
&
smtpSession
{
config
:
b
.
config
,
sub
:
b
.
sub
},
nil
return
&
smtpSession
{
backend
:
b
},
nil
}
func
(
b
*
smtpBackend
)
Counts
()
(
success
int64
,
failure
int64
)
{
b
.
mu
.
Lock
()
defer
b
.
mu
.
Unlock
()
return
b
.
success
,
b
.
failure
}
// smtpSession is returned after EHLO.
type
smtpSession
struct
{
config
*
Config
sub
subscriber
from
,
to
string
mu
sync
.
Mutex
backend
*
smtpBackend
topic
string
mu
sync
.
Mutex
}
func
(
s
*
smtpSession
)
AuthPlain
(
username
,
password
string
)
error
{
...
...
@@ -46,63 +59,84 @@ func (s *smtpSession) AuthPlain(username, password string) error {
}
func
(
s
*
smtpSession
)
Mail
(
from
string
,
opts
smtp
.
MailOptions
)
error
{
s
.
mu
.
Lock
()
defer
s
.
mu
.
Unlock
()
s
.
from
=
from
return
nil
}
func
(
s
*
smtpSession
)
Rcpt
(
to
string
)
error
{
s
.
mu
.
Lock
()
defer
s
.
mu
.
Unlock
()
addressList
,
err
:=
mail
.
ParseAddressList
(
to
)
if
err
!=
nil
{
return
err
}
else
if
len
(
addressList
)
!=
1
{
return
errors
.
New
(
"only one recipient supported"
)
}
else
if
!
strings
.
HasSuffix
(
addressList
[
0
]
.
Address
,
"@"
+
s
.
config
.
SMTPServerDomain
)
{
return
errors
.
New
(
"invalid domain"
)
}
else
if
s
.
config
.
SMTPServerAddrPrefix
!=
""
&&
!
strings
.
HasPrefix
(
addressList
[
0
]
.
Address
,
s
.
config
.
SMTPServerAddrPrefix
)
{
return
errors
.
New
(
"invalid address"
)
}
// FIXME check topic format
s
.
to
=
addressList
[
0
]
.
Address
return
nil
return
s
.
withFailCount
(
func
()
error
{
conf
:=
s
.
backend
.
config
addressList
,
err
:=
mail
.
ParseAddressList
(
to
)
if
err
!=
nil
{
return
err
}
else
if
len
(
addressList
)
!=
1
{
return
errTooManyRecipients
}
to
=
addressList
[
0
]
.
Address
if
!
strings
.
HasSuffix
(
to
,
"@"
+
conf
.
SMTPServerDomain
)
{
return
errInvalidDomain
}
to
=
strings
.
TrimSuffix
(
to
,
"@"
+
conf
.
SMTPServerDomain
)
if
conf
.
SMTPServerAddrPrefix
!=
""
{
if
!
strings
.
HasPrefix
(
to
,
conf
.
SMTPServerAddrPrefix
)
{
return
errInvalidAddress
}
to
=
strings
.
TrimPrefix
(
to
,
conf
.
SMTPServerAddrPrefix
)
}
if
!
topicRegex
.
MatchString
(
to
)
{
return
errInvalidTopic
}
s
.
mu
.
Lock
()
s
.
topic
=
to
s
.
mu
.
Unlock
()
return
nil
})
}
func
(
s
*
smtpSession
)
Data
(
r
io
.
Reader
)
error
{
s
.
mu
.
Lock
()
defer
s
.
mu
.
Unlock
()
b
,
err
:=
ioutil
.
ReadAll
(
r
)
if
err
!=
nil
{
return
err
}
log
.
Println
(
"Data:"
,
string
(
b
))
msg
,
err
:=
mail
.
ReadMessage
(
bytes
.
NewReader
(
b
))
if
err
!=
nil
{
return
err
}
body
,
err
:=
io
.
ReadAll
(
msg
.
Body
)
if
err
!=
nil
{
return
err
}
topic
:=
strings
.
TrimSuffix
(
s
.
to
,
"@"
+
s
.
config
.
SMTPServerDomain
)
m
:=
newDefaultMessage
(
topic
,
string
(
body
))
subject
:=
msg
.
Header
.
Get
(
"Subject"
)
if
subject
!=
""
{
m
.
Title
=
subject
}
return
s
.
sub
(
m
)
return
s
.
withFailCount
(
func
()
error
{
b
,
err
:=
io
.
ReadAll
(
r
)
// Protected by MaxMessageBytes
if
err
!=
nil
{
return
err
}
msg
,
err
:=
mail
.
ReadMessage
(
bytes
.
NewReader
(
b
))
if
err
!=
nil
{
return
err
}
body
,
err
:=
io
.
ReadAll
(
io
.
LimitReader
(
msg
.
Body
,
int64
(
s
.
backend
.
config
.
MessageLimit
)))
if
err
!=
nil
{
return
err
}
m
:=
newDefaultMessage
(
s
.
topic
,
string
(
body
))
subject
:=
msg
.
Header
.
Get
(
"Subject"
)
if
subject
!=
""
{
m
.
Title
=
subject
}
if
err
:=
s
.
backend
.
sub
(
m
);
err
!=
nil
{
return
err
}
s
.
backend
.
mu
.
Lock
()
s
.
backend
.
success
++
s
.
backend
.
mu
.
Unlock
()
return
nil
})
}
func
(
s
*
smtpSession
)
Reset
()
{
s
.
mu
.
Lock
()
s
.
from
=
""
s
.
to
=
""
s
.
topic
=
""
s
.
mu
.
Unlock
()
}
func
(
s
*
smtpSession
)
Logout
()
error
{
return
nil
}
func
(
s
*
smtpSession
)
withFailCount
(
fn
func
()
error
)
error
{
err
:=
fn
()
s
.
backend
.
mu
.
Lock
()
defer
s
.
backend
.
mu
.
Unlock
()
if
err
!=
nil
{
s
.
backend
.
failure
++
}
return
err
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment