Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SASL EXTERNAL and proxies for TLS connections #138

Merged
merged 2 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions examples/simple-tor.go/simple-tor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"github.com/thoj/go-ircevent"
"crypto/tls"
"log"
"os"
)

const addr = "libera75jm6of4wxpxt4aynol3xjmbtxgfyjpu34ss4d7r7q2v5zrpyd.onion:6697"

// This demos connecting to Libera.Chat over TOR using SASL EXTERNAL and a TLS
// client cert. It assumes a TOR SOCKS service is running on localhost:9050
// and requires an existing account with a fingerprint already registered. See
// https://libera.chat/guides/connect#accessing-liberachat-via-tor for details.
//
// Pass the full path to your cert and key on the command line like so:
// $ go run simple-tor.go my-nick my-cert.pem my-key.pem

func main() {
os.Setenv("ALL_PROXY", "socks5h://localhost:9050")
nick, certFile := os.Args[1], os.Args[2]
keyFile := certFile
if len(os.Args) == 4 {
keyFile = os.Args[3]
}
clientCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatal(err)
}
ircnick1 := nick
irccon := irc.IRC(ircnick1, nick)
irccon.VerboseCallbackHandler = true
irccon.UseSASL = true
irccon.SASLMech = "EXTERNAL"
irccon.Debug = true
irccon.UseTLS = true
irccon.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{clientCert},
}
irccon.AddCallback("001", func(e *irc.Event) {})
irccon.AddCallback("376", func(e *irc.Event) {
log.Println("Quitting")
irccon.Quit()
})
err = irccon.Connect(addr)
if err != nil {
log.Fatal(err)
}
irccon.Loop()
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/thoj/go-ircevent

go 1.12

require golang.org/x/text v0.3.2
require (
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/text v0.3.6
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
12 changes: 6 additions & 6 deletions irc.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"strings"
"time"

"golang.org/x/net/proxy"
"golang.org/x/text/encoding"
)

Expand Down Expand Up @@ -461,15 +462,14 @@ func (irc *Connection) Connect(server string) error {
return errors.New("empty 'user'")
}

if irc.UseTLS {
dialer := &net.Dialer{Timeout: irc.Timeout}
irc.socket, err = tls.DialWithDialer(dialer, "tcp", irc.Server, irc.TLSConfig)
} else {
irc.socket, err = net.DialTimeout("tcp", irc.Server, irc.Timeout)
}
dialer := proxy.FromEnvironmentUsing(&net.Dialer{Timeout: irc.Timeout})
irc.socket, err = dialer.Dial("tcp", irc.Server)
if err != nil {
return err
}
if irc.UseTLS {
irc.socket = tls.Client(irc.socket, irc.TLSConfig)
}

if irc.Encoding == nil {
irc.Encoding = encoding.Nop
Expand Down
8 changes: 6 additions & 2 deletions irc_sasl.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) (callbacks
}
}
if e.Arguments[1] == "ACK" && listContains(e.Arguments[2], "sasl") {
if irc.SASLMech != "PLAIN" {
result <- &SASLResult{true, errors.New("only PLAIN is supported")}
if irc.SASLMech != "PLAIN" && irc.SASLMech != "EXTERNAL" {
result <- &SASLResult{true, errors.New("only PLAIN and EXTERNAL supported")}
}
irc.SendRaw("AUTHENTICATE " + irc.SASLMech)
}
Expand All @@ -41,6 +41,10 @@ func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) (callbacks
callbacks = append(callbacks, CallbackID{"CAP", id})

id = irc.AddCallback("AUTHENTICATE", func(e *Event) {
if irc.SASLMech == "EXTERNAL" {
irc.SendRaw("AUTHENTICATE +")
return
}
str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword)))
irc.SendRaw("AUTHENTICATE " + str)
})
Expand Down
46 changes: 46 additions & 0 deletions irc_sasl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,49 @@ func TestConnectionSASL(t *testing.T) {
}
irccon.Loop()
}


// 1. Register fingerprint with IRC network
// 2. Add SASLKeyPem="-----BEGIN PRIVATE KEY-----..."
// and SASLCertPem="-----BEGIN CERTIFICATE-----..."
// to CI environment as masked variables
func TestConnectionSASLExternal(t *testing.T) {
SASLServer := "irc.freenode.net:7000"
keyPem := os.Getenv("SASLKeyPem")
certPem := os.Getenv("SASLCertPem")

if certPem == "" || keyPem == "" {
t.Skip("Env vars SASLKeyPem SASLCertPem not present, skipping")
}
if testing.Short() {
t.Skip("skipping test in short mode.")
}
cert, err := tls.X509KeyPair([]byte(certPem), []byte(keyPem))
if err != nil {
t.Fatalf("SASL EXTERNAL cert creation failed: %s", err)
}

irccon := IRC("go-eventirc", "go-eventirc")
irccon.VerboseCallbackHandler = true
irccon.Debug = true
irccon.UseTLS = true
irccon.UseSASL = true
irccon.SASLMech = "EXTERNAL"
irccon.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
}
irccon.AddCallback("001", func(e *Event) { irccon.Join("#go-eventirc") })

irccon.AddCallback("366", func(e *Event) {
irccon.Privmsg("#go-eventirc", "Test Message SASL EXTERNAL\n")
time.Sleep(2 * time.Second)
irccon.Quit()
})

err = irccon.Connect(SASLServer)
if err != nil {
t.Fatalf("SASL EXTERNAL failed: %s", err)
}
irccon.Loop()
}