mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-05-15 07:35:49 -06:00
Merge c1fa107f6d into d6397fc5e5
This commit is contained in:
commit
7e1019ca5a
2 changed files with 73 additions and 1 deletions
|
|
@ -1023,7 +1023,7 @@ you can set the `X-Email` header to [send messages via e-mail](publish.md#e-mail
|
|||
On ntfy.sh, anonymous email sending was disabled due to abuse. To use the email notification feature,
|
||||
you must verify your email in the web app's [Account section](https://ntfy.sh/account).
|
||||
|
||||
As of today, only SMTP servers with PLAIN auth and STARTLS are supported. To enable e-mail sending, you must set the
|
||||
As of today, SMTP servers with PLAIN and LOGIN auth and STARTLS are supported. To enable e-mail sending, you must set the
|
||||
following settings:
|
||||
|
||||
* `base-url` is the root URL for the ntfy server; this is needed for e-mail footer
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed" // required by go:embed
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/smtp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -15,11 +19,79 @@ import (
|
|||
"heckel.io/ntfy/v2/util"
|
||||
)
|
||||
|
||||
var (
|
||||
errUnencryptedConnection = errors.New("unencrypted connection")
|
||||
errWrongHostname = errors.New("wrong host name")
|
||||
errNoSupportedAuth = errors.New("no supported auth mechanisms found")
|
||||
errUnexpectedServerChallenge = errors.New("unexpected server challenge")
|
||||
)
|
||||
|
||||
type mailer interface {
|
||||
Send(v *visitor, m *model.Message, to string) error
|
||||
Counts() (total int64, success int64, failure int64)
|
||||
}
|
||||
|
||||
type plainOrLoginAuth struct {
|
||||
identity string
|
||||
username string
|
||||
password string
|
||||
host string
|
||||
authMethod string
|
||||
}
|
||||
|
||||
func PlainOrLoginAuth(identity, username, password, host string) smtp.Auth {
|
||||
return &plainOrLoginAuth{identity: identity, username: username, password: password, host: host}
|
||||
}
|
||||
|
||||
func isLocalhost(name string) bool {
|
||||
return name == "localhost" || name == "127.0.0.1" || name == "::1"
|
||||
}
|
||||
|
||||
func (a *plainOrLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
// Must have TLS, or else localhost server.
|
||||
// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
|
||||
// In particular, it doesn't matter if the server advertises PLAIN auth.
|
||||
// That might just be the attacker saying
|
||||
// "it's ok, you can trust me with your password."
|
||||
if !server.TLS && !isLocalhost(server.Name) {
|
||||
return "", nil, errUnencryptedConnection
|
||||
}
|
||||
if server.Name != a.host {
|
||||
return "", nil, errWrongHostname
|
||||
}
|
||||
|
||||
if slices.Contains(server.Auth, "PLAIN") {
|
||||
a.authMethod = "PLAIN"
|
||||
resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
|
||||
return a.authMethod, resp, nil
|
||||
} else if slices.Contains(server.Auth, "LOGIN") {
|
||||
a.authMethod = "LOGIN"
|
||||
return a.authMethod, nil, nil
|
||||
} else {
|
||||
return "", nil, errNoSupportedAuth
|
||||
}
|
||||
}
|
||||
|
||||
func (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if !more {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if a.authMethod == "PLAIN" {
|
||||
// We've already sent everything.
|
||||
return nil, errUnexpectedServerChallenge
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(fromServer, []byte("Username:")):
|
||||
return []byte(a.username), nil
|
||||
case bytes.Equal(fromServer, []byte("Password:")):
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errUnexpectedServerChallenge, string(fromServer))
|
||||
}
|
||||
}
|
||||
|
||||
type smtpSender struct {
|
||||
config *Config
|
||||
sender *mail.Sender
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue