Merge pull request #11 from opthomas-prime/mellium-xmpp

Mellium xmpp
master
Thomas Maier 2019-11-04 21:10:54 +01:00 zatwierdzone przez GitHub
commit 88c2f76d14
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
4 zmienionych plików z 147 dodań i 55 usunięć

Wyświetl plik

@ -10,6 +10,8 @@
- `XMPP_ID` - The JID we want to use
- `XMPP_PASS` - The password
- `XMPP_RECEIVERS` - Comma-seperated list of JID's
- `XMPP_SKIP_VERIFY` - Skip TLS verification (Optional)
- `XMPP_OVER_TLS` - Use deicated TLS port (Optional)
- After startup, `xmpp-webhook` tries to connect to the XMPP server and provides the implemented HTTP enpoints (on `:4321`). e.g.:
```

8
go.mod
Wyświetl plik

@ -1,5 +1,11 @@
module github.com/opthomas-prime/xmpp-webhook
require github.com/emgee/go-xmpp v0.0.0-20170414153234-efce8dbb9711
require (
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 // indirect
mellium.im/sasl v0.2.2-0.20190711145101-7aedd692081c
mellium.im/xmlstream v0.14.0
mellium.im/xmpp v0.14.0
)
go 1.13

36
go.sum
Wyświetl plik

@ -1,2 +1,34 @@
github.com/emgee/go-xmpp v0.0.0-20170414153234-efce8dbb9711 h1:BsL4akklVz02bS/nknhPwjwqtOyLjq3ZqUJT+IlqYgE=
github.com/emgee/go-xmpp v0.0.0-20170414153234-efce8dbb9711/go.mod h1:MM5R8Ii02ZFUaKj/z80DmESVPuCGJEqdOcGue7NwRmw=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss=
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss=
golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
mellium.im/reader v0.1.0 h1:UUEMev16gdvaxxZC7fC08j7IzuDKh310nB6BlwnxTww=
mellium.im/reader v0.1.0/go.mod h1:F+X5HXpkIfJ9EE1zHQG9lM/hO946iYAmU7xjg5dsQHI=
mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=
mellium.im/sasl v0.2.2-0.20190711145101-7aedd692081c h1:NjXK0TtVdkGhEghhYh3eA/f5nR/VRRrW1MrEEp1rq90=
mellium.im/sasl v0.2.2-0.20190711145101-7aedd692081c/go.mod h1:rTgGBJL0QZ3h4jRSAXRDTRB7h1b8GyyFpEG2t9Tp9ws=
mellium.im/xmlstream v0.13.5/go.mod h1:O7wqreSmFi1LOh4RiK7r2j4H4pYDgzo1qv5ZkYJZ7Ns=
mellium.im/xmlstream v0.13.6-0.20190729223506-de1fca5bec8b h1:Eh7dHb+bM/m0aj9PC7vXH2OnBXXTfokOF0PJ2QK1DBo=
mellium.im/xmlstream v0.13.6-0.20190729223506-de1fca5bec8b/go.mod h1:O7wqreSmFi1LOh4RiK7r2j4H4pYDgzo1qv5ZkYJZ7Ns=
mellium.im/xmlstream v0.14.0 h1:vTljQmcFQq7LEb+LJQV0VI8wnuFnzBy1AnfUbA4SrL8=
mellium.im/xmlstream v0.14.0/go.mod h1:O7wqreSmFi1LOh4RiK7r2j4H4pYDgzo1qv5ZkYJZ7Ns=
mellium.im/xmpp v0.13.1-0.20190803132429-9960aecd011f h1:Q40MWD6EXk+EmCHwMYr8YhYgKxETCQkF8jJNQ77ugyA=
mellium.im/xmpp v0.13.1-0.20190803132429-9960aecd011f/go.mod h1:ypSrEi/KFrDk9fu4JpQ8HKPFtlkVrhYCk1aObOMAof0=
mellium.im/xmpp v0.14.0 h1:pmeTKOze9IUD6VsYDjiKOdhQMb7+1Pc1loJnTDuRip0=
mellium.im/xmpp v0.14.0/go.mod h1:ZDObIk4gKWVdH45mRgna7UGRGa8xnzsvBnpCa4ujTAA=

156
main.go
Wyświetl plik

@ -1,78 +1,129 @@
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"
"github.com/emgee/go-xmpp/src/xmpp"
)
// starts xmpp session and returns the xmpp client
func xmppLogin(id string, pass string) (*xmpp.XMPP, error) {
// parse jid structure
jid, err := xmpp.ParseJID(id)
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, useXMPPS 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: !useXMPPS, TLSConfig: &tlsConfig}
} else {
dialer = dial.Dialer{NoTLS: !useXMPPS}
}
conn, err := dialer.Dial(context.TODO(), "tcp", address)
if err != nil {
return nil, err
}
// extract/generate address:port from jid
addr, err := xmpp.HomeServerAddrs(jid)
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(false, &tlsConfig),
xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
}}),
)
}
// create xml stream to address
stream, err := xmpp.NewStream(addr[0], nil)
if err != nil {
return nil, err
}
// create client (login)
client, err := xmpp.NewClientXMPP(stream, jid, pass, nil)
if err != nil {
return nil, err
}
return client, nil
func closeXMPP(session *xmpp.Session) {
session.Close()
session.Conn().Close()
}
func main() {
// get xmpp credentials and message receivers from env
// 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")
_, useXMPPS := os.LookupEnv("XMPP_OVER_TLS")
// check if xmpp credentials and receiver list are supplied
if len(xi) < 1 || len(xp) < 1 || len(xr) < 1 {
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
xc, err := xmppLogin(xi, xp)
if err != nil {
log.Fatal(err)
}
xmppSession, err := initXMPP(address, xp, skipTLSVerify, useXMPPS)
panicOnErr(err)
defer closeXMPP(xmppSession)
// announce initial presence
xc.Out <- xmpp.Presence{}
// send initial presence
panicOnErr(xmppSession.Send(context.TODO(), stanza.WrapPresence(jid.JID{}, stanza.AvailablePresence, nil)))
// listen for incoming xmpp stanzas
// listen for messages and echo them
go func() {
for stanza := range xc.In {
// check if stanza is a message
m, ok := stanza.(*xmpp.Message)
if ok && len(m.Body) > 0 {
// echo the message
xc.Out <- xmpp.Message{
To: m.From,
Body: m.Body,
}
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
}
}
// xc.In is closed when the server closes the stream
log.Fatal("connection lost")
// 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)
@ -82,14 +133,15 @@ func main() {
go func() {
for m := range messages {
for _, r := range strings.Split(xr, ",") {
xc.Out <- xmpp.Message{
To: r,
Body: []xmpp.MessageBody{
{
Value: m,
},
recipient, err := jid.Parse(r)
panicOnErr(err)
// try to send message, ignore errors
_ = xmppSession.Encode(MessageBody{
Message: stanza.Message{
To: recipient,
},
}
Body: m,
})
}
}
}()