xmpp-webhook/main.go

156 wiersze
3.7 KiB
Go

package main
import (
"context"
"crypto/tls"
"encoding/xml"
"io"
"log"
"mellium.im/sasl"
"mellium.im/xmlstream"
"mellium.im/xmpp"
"mellium.im/xmpp/dial"
"mellium.im/xmpp/jid"
"mellium.im/xmpp/stanza"
"net/http"
"os"
"strings"
)
func panicOnErr(err error) {
if err != nil {
panic(err)
}
}
type MessageBody struct {
stanza.Message
Body string `xml:"body"`
}
func initXMPP(address jid.JID, pass string, skipTLSVerify bool, legacyTLS bool, forceStartTLS bool) (*xmpp.Session, error) {
tlsConfig := tls.Config{InsecureSkipVerify: skipTLSVerify}
var dialer dial.Dialer
// only use the tls config for the dialer if necessary
if skipTLSVerify {
dialer = dial.Dialer{NoTLS: !legacyTLS, TLSConfig: &tlsConfig}
} else {
dialer = dial.Dialer{NoTLS: !legacyTLS}
}
conn, err := dialer.Dial(context.TODO(), "tcp", address)
if err != nil {
return nil, err
}
// we need the domain in the tls config if we want to verify the cert
if !skipTLSVerify {
tlsConfig.ServerName = address.Domainpart()
}
return xmpp.NegotiateSession(
context.TODO(),
address.Domain(),
address,
conn,
false,
xmpp.NewNegotiator(xmpp.StreamConfig{Features: []xmpp.StreamFeature{
xmpp.BindResource(),
xmpp.StartTLS(forceStartTLS, &tlsConfig),
xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
}}),
)
}
func closeXMPP(session *xmpp.Session) {
session.Close()
session.Conn().Close()
}
func main() {
// get xmpp credentials, message receivers
xi := os.Getenv("XMPP_ID")
xp := os.Getenv("XMPP_PASS")
xr := os.Getenv("XMPP_RECEIVERS")
// get tls settings from env
_, skipTLSVerify := os.LookupEnv("XMPP_SKIP_VERIFY")
_, legacyTLS := os.LookupEnv("XMPP_OVER_TLS")
_, forceStartTLS := os.LookupEnv("XMPP_FORCE_STARTTLS")
// check if xmpp credentials and receiver list are supplied
if xi == "" || xp == "" || xr == "" {
log.Fatal("XMPP_ID, XMPP_PASS or XMPP_RECEIVERS not set")
}
address, err := jid.Parse(xi)
panicOnErr(err)
// connect to xmpp server
xmppSession, err := initXMPP(address, xp, skipTLSVerify, legacyTLS, forceStartTLS)
panicOnErr(err)
defer closeXMPP(xmppSession)
// send initial presence
panicOnErr(xmppSession.Send(context.TODO(), stanza.WrapPresence(jid.JID{}, stanza.AvailablePresence, nil)))
// listen for messages and echo them
go func() {
err = xmppSession.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
d := xml.NewTokenDecoder(t)
// ignore elements that aren't messages
if start.Name.Local != "message" {
return nil
}
// parse message into struct
msg := MessageBody{}
err = d.DecodeElement(&msg, start)
if err != nil && err != io.EOF {
return nil
}
// ignore empty messages and stanzas that aren't messages
if msg.Body == "" || msg.Type != stanza.ChatMessage {
return nil
}
// create reply with identical contents
reply := MessageBody{
Message: stanza.Message{
To: msg.From.Bare(),
},
Body: msg.Body,
}
// try to send reply, ignore errors
_ = t.Encode(reply)
return nil
}))
panicOnErr(err)
}()
// create chan for messages (webhooks -> xmpp)
messages := make(chan string)
// wait for messages from the webhooks and send them to all receivers
go func() {
for m := range messages {
for _, r := range strings.Split(xr, ",") {
recipient, err := jid.Parse(r)
panicOnErr(err)
// try to send message, ignore errors
_ = xmppSession.Encode(MessageBody{
Message: stanza.Message{
To: recipient,
},
Body: m,
})
}
}
}()
// initialize handler for grafana alerts
http.Handle("/grafana", newMessageHandler(messages, grafanaParserFunc))
// listen for requests
http.ListenAndServe(":4321", nil)
}