pax_global_header00006660000000000000000000000064134035245270014517gustar00rootroot0000000000000052 comment=3e6f90796b23a7cb8ee7d706d769f161c1650576 robustirc-bridge-1.8/000077500000000000000000000000001340352452700146355ustar00rootroot00000000000000robustirc-bridge-1.8/.gitignore000066400000000000000000000011411340352452700166220ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof # Everything above is from https://raw.githubusercontent.com/github/gitignore/master/Go.gitignore # You should also follow https://help.github.com/articles/ignoring-files/ to # ignore vim/emacs/… files globally in git. # Everything below are specific additions, such as binary names. robustirc-bridge/robustirc-bridge ca-certificates.crt robustirc-bridge-1.8/.travis.yml000066400000000000000000000014121340352452700167440ustar00rootroot00000000000000# Use the (faster) container-based infrastructure, see also # http://docs.travis-ci.com/user/workers/container-based-infrastructure/ sudo: false language: go go: - 1.3 - 1.4 - 1.5 - 1.6 - 1.7 script: # Check whether files are syntactically correct. - "gofmt -l $(find . -name '*.go' | tr '\\n' ' ') >/dev/null" # Check whether files were not gofmt'ed. - "gosrc=$(find . -name '*.go' | tr '\\n' ' '); [ $(gofmt -l $gosrc 2>&- | wc -l) -eq 0 ] || (echo 'gofmt was not run on these files:'; gofmt -l $gosrc 2>&-; false)" # We intentionally do not use “go tool vet”, because vet reports issues for # go ≥ 1.3, but some of these (e.g. unreached code) is required for # compilation with go1.0.2. - go get github.com/robustirc/bridge/robustirc-bridge robustirc-bridge-1.8/AUTHORS000066400000000000000000000004701340352452700157060ustar00rootroot00000000000000# This is the official list of RobustIRC authors for copyright purposes. # # Names should be added to this file as # Name or Organization # The IRC nickname is not required for organizations. # # Please keep the list sorted. Axel Wagner Michael Stapelberg robustirc-bridge-1.8/Dockerfile000066400000000000000000000015511340352452700166310ustar00rootroot00000000000000# Start with busybox, but with libc.so.6 FROM busybox:glibc MAINTAINER Michael Stapelberg # So that we can run as unprivileged user inside the container. RUN echo 'nobody:x:99:99:nobody:/:/bin/sh' >> /etc/passwd USER nobody ADD ca-certificates.crt /etc/ssl/certs/ca-certificates.crt ADD robustirc-bridge/robustirc-bridge /usr/bin/robustirc-bridge ADD bridge-motd.txt /usr/share/robustirc/bridge-motd.txt # For public bridges (legacy-irc.), you should only expose port 6667. # For private installations, you may also expose 1080, allowing users to # connect to arbitrary RobustIRC networks (regardless of -network). EXPOSE 6667 1080 # The following flags have to be specified when starting this container: # -network # Refer to -help for documentation on them. ENTRYPOINT ["/usr/bin/robustirc-bridge", "-listen=:6667", "-socks=:1080"] robustirc-bridge-1.8/LICENSE000066400000000000000000000027611340352452700156500ustar00rootroot00000000000000Copyright © 2014-2015 The RobustIRC Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of RobustIRC nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. robustirc-bridge-1.8/Makefile000066400000000000000000000012651340352452700163010ustar00rootroot00000000000000# Building with “go build” will work just fine. # This file just exists to build Docker containers. .PHONY: container all: @echo This Makefile is only for building docker containers. @echo Please follow the instructions on http://robustirc.net for running the bridge. container: (cd robustirc-bridge && go build) # This list is from go/src/crypto/x509/root_unix.go. install $(shell ls \ /etc/ssl/certs/ca-certificates.crt \ /etc/pki/tls/certs/ca-bundle.crt \ /etc/ssl/ca-bundle.pem \ /etc/ssl/cert.pem \ /usr/local/share/certs/ca-root-nss.crt \ /etc/pki/tls/cacert.pem \ /etc/certs/ca-certificates.crt \ 2>&- | head -1) ca-certificates.crt docker build --rm -t=robustirc/bridge . robustirc-bridge-1.8/README.md000066400000000000000000000000751340352452700161160ustar00rootroot00000000000000Please see http://robustirc.net/docs.html for documentation. robustirc-bridge-1.8/bridge-motd.txt000066400000000000000000000012101340352452700175650ustar00rootroot00000000000000───────────────────────────────────────────────────────────────── You connected to a public RobustIRC bridge. BY NOT RUNNING A BRIDGE ON THE SAME MACHINE AS YOUR IRC CLIENT, YOU DONT GET TRANSPARENT HANDLING OF NETWORK CONNECTION FAILURES. Please run your own bridge instead. For instructions, see http://robustirc.net/ ───────────────────────────────────────────────────────────────── robustirc-bridge-1.8/go.mod000066400000000000000000000001111340352452700157340ustar00rootroot00000000000000module github.com/robustirc/bridge require github.com/sorcix/irc v1.1.4 robustirc-bridge-1.8/go.sum000066400000000000000000000002411340352452700157650ustar00rootroot00000000000000github.com/sorcix/irc v1.1.4 h1:KDmVMPPzK4kbf3TQw1RsZAqTsh2JL9Zw69hYduX9Ykw= github.com/sorcix/irc v1.1.4/go.mod h1:MhzbySH63tDknqfvAAFK3ps/942g4z9EeJ/4lGgHyZc= robustirc-bridge-1.8/robustirc-bridge.1000066400000000000000000000046711340352452700201750ustar00rootroot00000000000000.de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .TH robustirc-bridge 1 "JANUARY 2015" Linux "User Manuals" .SH NAME robustirc-bridge \- bridge between IRC and RobustIRC .SH SYNOPSIS .B robustirc-bridge .RB [\|\-listen .IR address \|] .RB [\|\-network .IR address \|] .RB [\|\-socks .IR address \|] .RB [\|\-tls_ca_file .IR path \|] .RB [\|\-tls_cert_path .IR path \|] .RB [\|\-tls_key_path .IR path \|] .RB [\|\-motd_path .IR path \|] .SH DESCRIPTION .B robustirc-bridge allows you to connect to a RobustIRC network using your regular IRC client (such as irssi, WeeChat, XChat, etc). It can be used as a SOCKS proxy for IRC clients which support SOCKS proxies, or it can be run for a specific RobustIRC network, in which case it will listen on a port (localhost:6667 by default) and talk IRC. .SH OPTIONS .TP .BI \-listen\ address \fR Listen on the provided address for IRC connections (in host:port format, see also http://golang.org/pkg/net/#Dial for more details). \fB-network\fR must also be specified for \fB-listen\fR to have any effect. .TP .BI \-network\ address \fR When accepting new connections on the port specified by \fB-listen\fR, to which network should they be bridged? This is a DNS name such as "robustirc.net". The servers to connect to will be resolved by resolving the _robustirc._tcp SRV record for that DNS name. If \fB-network\fR is unspecified, \fB-listen\fR will not be used, and only the \fB-socks\fR functionality will be provided. .TP .BI \-socks\ address \fR Listen on the provided address for SOCKS connections (in host:port format, see also http://golang.org/pkg/net/#Dial for more details). .TP .BI \-motd_path\ path \fR Path to a text file containing the message of the day (MOTD) to prefix to the network MOTD. The default MOTD warns people that they should run their own bridge instead of connecting, and you are expected to specify \fB-motd_path=\fR (i.e. empty) when starting the bridge in a scenario where you are the only user or all users run their IRC client on the same machine as the bridge. .TP .BI \-tls_cert_path\ path \fR Path to a .pem file containing the TLS certificate. If unspecified, TLS is not used. .TP .BI \-tls_key_path\ path \fR Path to a .pem file containing the TLS private key. If unspecified, TLS is not used. .TP .BI \-tls_ca_file\ path \fR Use the specified file as trusted CA instead of the system CAs. Useful for testing. .SH AUTHOR Michael Stapelberg robustirc-bridge-1.8/robustirc-bridge.service000066400000000000000000000012501340352452700214630ustar00rootroot00000000000000[Unit] Description=RobustIRC bridge Documentation=man:robustirc-bridge(1) Documentation=http://robustirc.net/ [Service] Restart=on-failure # By default, the RobustIRC bridge is started in SOCKS proxy mode only. # Enable robustirc-bridge@.service with the network name for IRC listening # mode, e.g. systemctl enable --now robustirc-bridge@robustirc.net.service ExecStart=/usr/bin/robustirc-bridge -socks=localhost:1080 -listen= # The bridge only needs network access and is entirely stateless. Therefore, # restrict access to the system as far as possible. User=nobody PrivateDevices=true ProtectSystem=true ProtectHome=true PrivateTmp=true [Install] WantedBy=multi-user.target robustirc-bridge-1.8/robustirc-bridge/000077500000000000000000000000001340352452700201035ustar00rootroot00000000000000robustirc-bridge-1.8/robustirc-bridge/bridge.go000066400000000000000000000352151340352452700216740ustar00rootroot00000000000000// bridge bridges between IRC clients (RFC1459) and RobustIRC servers. // // Bridge instances are supposed to be long-running, and ideally as close to the // IRC client as possible, e.g. on the same machine. When running on the same // machine, there should not be any network problems between the IRC client and // the bridge. Network problems between the bridge and a RobustIRC network are // handled transparently. package main import ( "bufio" "crypto/tls" "flag" "fmt" "io" "io/ioutil" "log" "math/rand" "net" "net/http" _ "net/http/pprof" "os" "os/signal" "strings" "syscall" "time" "github.com/robustirc/bridge/robustsession" "github.com/sorcix/irc" // Necessary on go1.0.2 (debian wheezy) to make crypto/tls work with the // certificates on robustirc.net (and possibly others). _ "crypto/sha1" _ "crypto/sha256" _ "crypto/sha512" ) var ( network = flag.String("network", "", `DNS name to connect to (e.g. "robustirc.net"). The _robustirc._tcp SRV record must be present.`) listen = flag.String("listen", "localhost:6667", "comma-separated list of host:port tuples to listen on for IRC connections. You must also specify -network for -listen to work (or use SOCKS instead)") socks = flag.String("socks", "localhost:1080", "host:port to listen on for SOCKS5 connections") httpAddress = flag.String("http", "", "(for debugging) host:port to listen on for HTTP connections, exposing /debug/pprof") tlsCertPath = flag.String("tls_cert_path", "", "Path to a .pem file containing the TLS certificate. If unspecified, TLS is not used.") tlsKeyPath = flag.String("tls_key_path", "", "Path to a .pem file containing the TLS private key. If unspecified, TLS is not used.") tlsCAFile = flag.String("tls_ca_file", "", "Use the specified file as trusted CA instead of the system CAs. Useful for testing.") motdPath = flag.String("motd_path", "/usr/share/robustirc/bridge-motd.txt", "Path to a text file containing the message of the day (MOTD) to prefix to the network MOTD.") authPath = flag.String("bridge_auth", "", "Path to a text file containing one network:secretkey pair per line to authenticate this bridge against the configured RobustIRC networks.") version = flag.Bool("version", false, "Print version and exit.") ) // TODO(secure): persistent state: // - the last known server(s) in the network. added to *servers // - for resuming sessions (later): the last seen message id, perhaps setup messages (JOINs, MODEs, …) // for hosted mode, this state is stored per-nickname, ideally encrypted with password // prefixMotd takes an irc.MOTD message from the server and prefixes it with // our own MOTD. E.g., it takes: // :robustirc.net 372 sECuRE :- First line of MOTD\r\n // and turns that into: // :robustirc.net 372 sECuRE :- First line of bridge MOTD\r\n // :robustirc.net 372 sECuRE :- Thanks for using this bridge! Enjoy!\r\n // :robustirc.net 372 sECuRE :- First line of MOTD\r\n func prefixMotd(msg string) string { // The user chose to not inject a MOTD. if *motdPath == "" { return msg } sep := strings.Index(msg[1:], ":") if sep == -1 { return msg } prefix := msg[:sep+2] + "- " f, err := os.Open(*motdPath) if err != nil { log.Printf("Cannot inject MOTD: %v\n", err) return msg } defer f.Close() var injected []string reader := bufio.NewReader(f) for { line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { break } log.Printf("Cannot inject MOTD: %v\n", err) return msg } if len(line) > 0 && line[len(line)-1] == '\r' { line = line[:len(line)-1] } injected = append(injected, prefix+line) } return strings.Join(injected, "\r\n") + "\r\n" + msg } type bridge struct { network string auth string } func getAuth(path, network string) (string, error) { if path == "" { return "", nil } st, err := os.Stat(path) if err != nil { return "", err } if st.Mode()&007 != 0 { return "", fmt.Errorf("-bridge_auth=%q has insecure permissions %o, fix with chmod o-rwx %q", path, st.Mode(), path) } authBytes, err := ioutil.ReadFile(path) if err != nil { return "", err } networkPrefix := network + ":" for _, line := range strings.Split(string(authBytes), "\n") { if strings.HasPrefix(line, networkPrefix) { auth := line[len(networkPrefix):] if got, want := len(auth), 32; got < want { return "", fmt.Errorf("Authentication data for network %q in %q is too short: got %d, want at least %d bytes", network, path, got, want) } return auth, nil } } return "", nil } func newBridge(network string) *bridge { auth, err := getAuth(*authPath, network) if err != nil { log.Printf("Could not get authentication data for network %q: %v", network, err) } return &bridge{ network: network, auth: auth, } } type ircsession struct { Messages chan irc.Message Errors chan error conn *irc.Conn rawConn net.Conn } func newIrcsession(conn net.Conn) *ircsession { s := &ircsession{ Messages: make(chan irc.Message), Errors: make(chan error), conn: irc.NewConn(conn), rawConn: conn, } go s.getMessages() return s } func (s *ircsession) Send(msg []byte) error { if _, err := s.conn.Write(msg); err != nil { return err } return nil } func (s *ircsession) Delete(killmsg string) error { defer s.conn.Close() // Read all remaining values to ensure nobody is blocked on sending. defer func() { go func() { for _ = range s.Messages { } }() go func() { for _ = range s.Errors { } }() }() if killmsg != "" { return s.conn.Encode(&irc.Message{ Command: "ERROR", Trailing: killmsg, }) } return nil } func (s *ircsession) getMessages() { defer close(s.Messages) defer close(s.Errors) // Read the first byte. If it’s 4 or 5, this is likely SOCKS first := make([]byte, 1) if _, err := s.rawConn.Read(first); err != nil { s.Errors <- err return } // %x04 or %x05 as the first byte is both invalid according to RFC2812 // section 2.3.1. Valid characters are ":" (%x3A) or %x30-39 (0-9) or // %x41-5A / %x61-7A (A-Z / a-z). // So we can just close the connection here, and also log that the user is // most likely trying to use SOCKS on the wrong port. // // TODO(secure): With some refactoring, we might even just handle the SOCKS // connection properly. if first[0] == 4 || first[0] == 5 { log.Printf("Read 0x%02x as first byte, which looks like a SOCKS version number. Please connect to %q instead of %q.\n", first[0], *socks, *listen) s.Errors <- fmt.Errorf("Read 0x%02x (SOCKS version?) as first byte on an IRC connection", first[0]) return } line := []byte{first[0]} for first[0] != '\n' { if _, err := s.rawConn.Read(first); err != nil { s.Errors <- err return } line = append(line, first[0]) } ircmsg := irc.ParseMessage(string(line)) if ircmsg != nil { s.Messages <- *ircmsg } for { ircmsg, err := s.conn.Decode() if err != nil { s.Errors <- err return } // Skip invalid lines (to prevent nil pointer dereferences). if ircmsg == nil { continue } s.Messages <- *ircmsg } } func (p *bridge) handleIRC(conn net.Conn) { var quitmsg, killmsg string var waitingForPingReply bool ircSession := newIrcsession(conn) defer func() { log.Printf("Terminating IRC connection from %s. killmsg=%q\n", conn.RemoteAddr(), killmsg) if err := ircSession.Delete(killmsg); err != nil { log.Printf("Could not properly delete IRC session: %v\n", err) } // The separator makes it easier to read logs when the client is // reconnecting in a loop (which is the most common situation in which // you’re interested in the logs at all). log.Printf("\n") }() robustSession, err := robustsession.Create(p.network, *tlsCAFile) if err != nil { killmsg = fmt.Sprintf("Could not create RobustIRC session: %v", err) return } robustSession.BridgeAuth = p.auth robustSession.ForwardedFor = conn.RemoteAddr().String() log.Printf("[session %s] Created RobustSession for client %s\n", robustSession.SessionId(), conn.RemoteAddr()) defer func() { log.Printf("[session %s] Deleting RobustSession. quitmsg=%q\n", robustSession.SessionId(), quitmsg) if err := robustSession.Delete(quitmsg); err != nil { log.Printf("Could not properly delete RobustIRC session: %v\n", err) } }() var sendIRC, sendRobust []byte keepalivePong := ":" + robustSession.IrcPrefix.String() + " PONG keepalive" // like keepalivePong, but with the trailing parameter encoded using the // prefix separator (as sorcix/irc.v2 does, well in accordance with // RFC1459). keepalivePongTrailing := ":" + robustSession.IrcPrefix.String() + " PONG :keepalive" motdPrefix := ":" + robustSession.IrcPrefix.String() + " 372 " welcomePrefix := ":" + robustSession.IrcPrefix.String() + " 001 " welcomed := false motdInjected := false signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGTERM) keepaliveToNetwork := time.After(1 * time.Minute) keepaliveToClient := time.After(1 * time.Minute) for { // These two variables contain the messages to be sent to IRC/RobustIRC // from the previous iteration of the state machine. That way, there is // only one place where the error handling happens. if sendIRC != nil { if err := ircSession.Send(sendIRC); err != nil { quitmsg = fmt.Sprintf("Bridge: Send to IRC client: %v", err) return } keepaliveToClient = time.After(1 * time.Minute) sendIRC = nil } if sendRobust != nil { if err := robustSession.PostMessage(string(sendRobust)); err != nil { killmsg = fmt.Sprintf("Could not post message to RobustIRC: %v", err) return } keepaliveToNetwork = time.After(1 * time.Minute) sendRobust = nil } select { case sig := <-signalChan: killmsg = fmt.Sprintf("Bridge exiting upon receiving signal (%v)", sig) quitmsg = killmsg return case msg := <-robustSession.Messages: // Inject the bridge’s message of the day. if !motdInjected && strings.HasPrefix(msg, motdPrefix) { sendIRC = []byte(prefixMotd(msg)) break } // For debugging purposes, print a log message when the client successfully logs into IRC. if !welcomed && strings.HasPrefix(msg, welcomePrefix) { log.Printf("[session %s] Successfully logged into IRC.\n", robustSession.SessionId()) welcomed = true } if msg == keepalivePong || msg == keepalivePongTrailing { break } sendIRC = []byte(msg) case err := <-robustSession.Errors: killmsg = fmt.Sprintf("RobustIRC session error: %v", err) return case ircmsg := <-ircSession.Messages: switch strings.ToUpper(ircmsg.Command) { case irc.PONG: waitingForPingReply = false case irc.PING: sendIRC = (&irc.Message{ Prefix: robustSession.IrcPrefix, Command: irc.PONG, Params: ircmsg.Params, Trailing: ircmsg.Trailing, }).Bytes() case irc.QUIT: // Only interpret this as QUIT when it’s coming directly as a // command, not as a server-to-server message. if ircmsg.Prefix == nil { quitmsg = ircmsg.Trailing return } fallthrough default: sendRobust = ircmsg.Bytes() } case err := <-ircSession.Errors: quitmsg = fmt.Sprintf("Bridge: Read from IRC client: %v", err) return case <-keepaliveToClient: // After no traffic in either direction for 1 minute, we send a PING // message. If a PING message was already sent, this means that we did // not receive a PONG message, so we close the connection with a // timeout. if waitingForPingReply { quitmsg = "Bridge: ping timeout" return } sendIRC = (&irc.Message{ Command: irc.PING, Params: []string{"robustirc.bridge"}, }).Bytes() waitingForPingReply = true case <-keepaliveToNetwork: sendRobust = []byte("PING keepalive") } } } // Copied from src/net/http/server.go type tcpKeepAliveListener struct { *net.TCPListener } func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { tc, err := ln.AcceptTCP() if err != nil { return } tc.SetKeepAlive(true) return tc, nil } // maybeTLSListener returns a net.Listener which possibly uses TLS, depending // on the -tls_cert_path and -tls_key_path flag values. func maybeTLSListener(addr string) net.Listener { if *tlsCertPath == "" || *tlsKeyPath == "" { ln, err := net.Listen("tcp", addr) if err != nil { log.Fatal(err) } return ln } tlsconfig, err := makeTlsConfig() if err != nil { log.Fatal(err) } ln, err := net.Listen("tcp", addr) if err != nil { log.Fatal(err) } return tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsconfig) } func main() { flag.Parse() if os.Getenv("GOKRAZY_FIRST_START") == "1" { os.Exit(125) // on gokrazy.org, this program must be run via a wrapper } if *version { fmt.Println(robustsession.Version) return } rand.Seed(time.Now().Unix()) if (*network == "" && *socks == "") || (*socks == "" && *listen == "") { log.Fatal("You must specify either -network and -listen, or -socks.") } if *httpAddress != "" { go func() { log.Printf("-http listener failed: %v\n", http.ListenAndServe(*httpAddress, nil)) }() } var listeners []net.Listener signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGTERM) go func() { sig := <-signalChan closeTimeout := 1 * time.Second log.Printf("Received signal %q, giving connections %v to close\n", sig, closeTimeout) for _, ln := range listeners { ln.Close() } time.Sleep(closeTimeout) log.Printf("Exiting due to signal %q\n", sig) os.Exit(int(syscall.SIGTERM) | 0x80) }() // SOCKS and IRC if *socks != "" && *network != "" && *listen != "" { ln := maybeTLSListener(*socks) listeners = append(listeners, ln) go func() { log.Printf("RobustIRC IRC bridge listening on %q (SOCKS). Specify an empty -socks= to disable.\n", *socks) if err := serveSocks(ln); err != nil { log.Fatal(err) } }() } // SOCKS only if *socks != "" && (*network == "" || *listen == "") { log.Printf("RobustIRC IRC bridge listening on %q (SOCKS). Specify an empty -socks= to disable.\n", *socks) log.Printf("Not listening on %q (IRC) because -network= was not specified.\n", *listen) ln := maybeTLSListener(*socks) listeners = append(listeners, ln) log.Fatal(serveSocks(ln)) } // IRC if *network != "" && *listen != "" { p := newBridge(*network) for _, addr := range strings.Split(*listen, ",") { ln := maybeTLSListener(addr) listeners = append(listeners, ln) log.Printf("RobustIRC IRC bridge listening on %q (IRC)\n", addr) go func() { for { conn, err := ln.Accept() if err != nil { log.Printf("Could not accept IRC client connection: %v\n", err) // Avoid flooding the logs with failed Accept()s. time.Sleep(1 * time.Second) continue } go p.handleIRC(conn) } }() } // Sleep forever <-make(chan struct{}) } } robustirc-bridge-1.8/robustirc-bridge/socks.go000066400000000000000000000122241340352452700215550ustar00rootroot00000000000000package main import ( "encoding/binary" "errors" "fmt" "log" "net" "time" ) const ( socksStatusGranted = iota socksStatusGeneralFailure socksStatusNotAllowed socksStatusNetworkUnreachable socksStatusHostUnreachable socksStatusConnectionRefused socksStatusTTLExpired socksStatusCommandNotSupported socksStatusAddressTypeNotSupported socksStatusProtocolError = socksStatusCommandNotSupported ) const ( _ = iota socksCommandConnectTCP socksCommandBind socksCommandConnectUDP ) const ( _ = iota socksAddrIPv4 _ socksAddrDNS socksAddrIPv6 ) const ( socksAuthNone = iota ) type socksServer struct { conn net.Conn } type socksConnectionData struct { Version uint8 Command uint8 // Status in Response Reserved uint8 AddrType uint8 Addr []byte Port uint16 } func serveSocks(ln net.Listener) error { for { conn, err := ln.Accept() if err != nil { log.Printf("Could not accept SOCKS client connection: %v\n", err) // Avoid flooding the logs with failed Accept()s. time.Sleep(1 * time.Second) continue } go func() { s := &socksServer{conn} if err := s.handleConn(); err != nil { log.Printf("Could not SOCKS: %v\n", err) } }() } // Unreached, but necessary for compiling with go1.0.2 (debian stable). return nil } func (s *socksServer) handleConn() (err error) { defer s.conn.Close() if err = s.greet(); err != nil { return err } var req *socksConnectionData if req, err = s.readRequest(); err != nil { return err } log.Printf("Got SOCKS request %#v\n", *req) // Invalid SOCKS version if req.Version != 5 { req.Command = socksStatusProtocolError s.sendResponse(req) return fmt.Errorf("unsupported SOCKS version %d", req.Version) } // Not a CONNECT command if req.Command != socksCommandConnectTCP { req.Command = socksStatusCommandNotSupported s.sendResponse(req) return fmt.Errorf("unsupported SOCKS command %d", req.Command) } // Not a Domain name if req.AddrType != socksAddrDNS { req.Command = socksStatusAddressTypeNotSupported s.sendResponse(req) return fmt.Errorf("unsupported SOCKS addr type %d", req.AddrType) } // First byte of address is the length p := newBridge(string(req.Addr[1:])) req.Command = socksStatusGranted if err := s.sendResponse(req); err != nil { return err } p.handleIRC(s.conn) // never returns return nil } func (s *socksServer) greet() error { type clientGreeting struct { Version uint8 NumAuth uint8 } var g clientGreeting if err := binary.Read(s.conn, binary.BigEndian, &g); err != nil { return fmt.Errorf("could not read SOCKS5-header: %v", err) } if g.Version != 5 { return fmt.Errorf("unsupported SOCKS version %d", g.Version) } // Read supported authentication methods auths := make([]byte, g.NumAuth) if _, err := s.conn.Read(auths); err != nil { return fmt.Errorf("could not read authentication methods: %v", err) } var noAuth bool for _, a := range auths { if a == socksAuthNone { noAuth = true break } } type serverAnswer struct { Version uint8 Auth uint8 } a := serverAnswer{ Version: 5, Auth: 0, } if !noAuth { a.Auth = 0xFF } if err := binary.Write(s.conn, binary.BigEndian, a); err != nil { return fmt.Errorf("could not send: %v", err) } if !noAuth { return errors.New("no supported authentication methods") } return nil } // readRequest reads a SOCKS5 connection request from the connection and // returns it. A returned request of nil means an error reading from the // socket. func (s *socksServer) readRequest() (req *socksConnectionData, err error) { type connRequestHeader struct { Version uint8 Command uint8 Reserved uint8 AddrType uint8 } var addrData []byte var h connRequestHeader if err := binary.Read(s.conn, binary.BigEndian, &h); err != nil { return nil, err } switch h.AddrType { case 1: // IPv4 address addrData = make([]byte, 4) _, err = s.conn.Read(addrData) case 3: // Domain name var addrLen uint8 if err := binary.Read(s.conn, binary.BigEndian, &addrLen); err != nil { return nil, err } addrData = make([]byte, addrLen+1) addrData[0] = addrLen _, err = s.conn.Read(addrData[1:]) case 4: // IPv6 address addrData = make([]byte, 16) _, err = s.conn.Read(addrData) default: } if err != nil { return nil, err } var port uint16 if err := binary.Read(s.conn, binary.BigEndian, &port); err != nil { return nil, err } return &socksConnectionData{ Version: h.Version, Command: h.Command, Reserved: h.Reserved, AddrType: h.AddrType, Addr: addrData, Port: port, }, nil } func (s *socksServer) sendResponse(res *socksConnectionData) error { // Send response before we are done log.Printf("Sending response: %#v\n", res) type responseHeader struct { Version uint8 Status uint8 Reserved uint8 AddrType uint8 } r := responseHeader{ Version: res.Version, Status: res.Command, Reserved: res.Reserved, AddrType: res.AddrType, } if err := binary.Write(s.conn, binary.BigEndian, &r); err != nil { return err } if _, err := s.conn.Write(res.Addr); err != nil { return err } if err := binary.Write(s.conn, binary.BigEndian, res.Port); err != nil { return err } return nil } robustirc-bridge-1.8/robustirc-bridge/tlsconfig_dummy.go000066400000000000000000000004451340352452700236400ustar00rootroot00000000000000// +build !go1.4 package main import "crypto/tls" func makeTlsConfig() (*tls.Config, error) { tlsconfig := &tls.Config{ Certificates: make([]tls.Certificate, 1), } var err error tlsconfig.Certificates[0], err = tls.LoadX509KeyPair(*tlsCertPath, *tlsKeyPath) return tlsconfig, err } robustirc-bridge-1.8/robustirc-bridge/tlsconfig_go1.4.go000066400000000000000000000005021340352452700233270ustar00rootroot00000000000000// +build go1.4 package main import ( "crypto/tls" "github.com/robustirc/bridge/tlsutil" ) func makeTlsConfig() (*tls.Config, error) { kpr, err := tlsutil.NewKeypairReloader(*tlsCertPath, *tlsKeyPath) if err != nil { return nil, err } return &tls.Config{ GetCertificate: kpr.GetCertificateFunc(), }, nil } robustirc-bridge-1.8/robustirc-bridge@.service000066400000000000000000000010111340352452700215560ustar00rootroot00000000000000[Unit] Description=RobustIRC bridge (for %I) Documentation=man:robustirc-bridge(1) Documentation=http://robustirc.net/ [Service] Restart=on-failure # robustirc-bridge.service (without @) listens on SOCKS already. ExecStart=/usr/bin/robustirc-bridge -socks= -network=%I # The bridge only needs network access and is entirely stateless. Therefore, # restrict access to the system as far as possible. User=nobody PrivateDevices=true ProtectSystem=true ProtectHome=true PrivateTmp=true [Install] WantedBy=multi-user.target robustirc-bridge-1.8/robustsession/000077500000000000000000000000001340352452700175575ustar00rootroot00000000000000robustirc-bridge-1.8/robustsession/deadlineconn.go000066400000000000000000000046301340352452700225340ustar00rootroot00000000000000package robustsession import ( "net" "time" ) // deadlineConn wraps a net.Conn and calls SetReadDeadline or SetWriteDeadline // before doing the actual Read and Write call, respectively. The deadline is // set to now+|timeout|. // // This is useful for long-running connections (such as for the GetMessages // request) which cannot have a timeout for the entire operation, but still // benefit from timeouts for the individual Read and Write calls. This behavior // is also called “idle timeout” sometimes. // // See http://stackoverflow.com/a/5509956/712014 for why a write deadline is // necessary. If we did not have a write deadline, data could accumulate in the // kernel socket buffer and just indefinitely block our Write() call. type deadlineConn struct { net.Conn timeout time.Duration } func NewDeadlineConn(conn net.Conn, timeout time.Duration) *deadlineConn { return &deadlineConn{ conn, timeout, } } func (d *deadlineConn) Read(b []byte) (int, error) { if err := d.SetReadDeadline(time.Now().Add(d.timeout)); err != nil { return 0, err } return d.Conn.Read(b) } func (d *deadlineConn) Write(b []byte) (int, error) { if err := d.SetWriteDeadline(time.Now().Add(d.timeout)); err != nil { return 0, err } return d.Conn.Write(b) } // DeadlineConnDialer returns a net.Dialer (actual interface type unused // because it is not covered by go1) which wraps all net.Conns in a // deadlineConn with the specified |timeout|, applied to both, Read and Write // calls. The dialing itself must be done within |dialTimeout| and TCP // keepalive is enabled (if compiled with go1.2+) with a period of // |keepalivePeriod|. func DeadlineConnDialer(dialTimeout, keepalivePeriod, timeout time.Duration) func(string, string) (net.Conn, error) { return func(network, address string) (net.Conn, error) { conn, err := dualStackDialTimeout(network, address, dialTimeout) if err != nil { return nil, err } // In addition to setting a read deadline to detect problems on the // application level (e.g. a server deadlock), we also enable TCP // keepalive on all connections. The additional benefit is that // keepalive packets are sent in a shorter interval and possibly with a // different start time due to net/http’s connection pooling and // re-use. Therefore, network layer problems might be detected more // quickly. setupKeepAlive(conn, keepalivePeriod) return NewDeadlineConn(conn, timeout), nil } } robustirc-bridge-1.8/robustsession/doc_test.go000066400000000000000000000014451340352452700217160ustar00rootroot00000000000000package robustsession_test import ( "log" "github.com/robustirc/bridge/robustsession" ) func ExampleRobustSession() { session, err := robustsession.Create("robustirc.net", "") if err != nil { log.Fatalf("Could not create robustsession: %v", err) } go func() { for msg := range session.Messages { log.Printf("<- %s\n", msg) } }() go func() { for err := range session.Errors { log.Fatalf("RobustSession error: %v", err) } }() input := []string{ "NICK example", "USER docs * 0 :Example User", "JOIN #robustirc", "PRIVMSG #robustirc :trying out the example :)", "QUIT :woohoo", "PRIVMSG #robustirc :this will trigger an error", } for _, msg := range input { log.Printf("-> %s\n", msg) if err := session.PostMessage(msg); err != nil { log.Fatal(err) } } } robustirc-bridge-1.8/robustsession/dualstack_dummy.go000066400000000000000000000003371340352452700232770ustar00rootroot00000000000000// +build !go1.2 package robustsession import ( "net" "time" ) func dualStackDialTimeout(network, address string, dialTimeout time.Duration) (net.Conn, error) { return net.DialTimeout(network, address, dialTimeout) } robustirc-bridge-1.8/robustsession/dualstack_go1.2.go000066400000000000000000000004121340352452700227640ustar00rootroot00000000000000// +build go1.2 package robustsession import ( "net" "time" ) func dualStackDialTimeout(network, address string, dialTimeout time.Duration) (net.Conn, error) { d := net.Dialer{ Timeout: dialTimeout, DualStack: true, } return d.Dial(network, address) } robustirc-bridge-1.8/robustsession/keepalive_dummy.go000066400000000000000000000002011340352452700232570ustar00rootroot00000000000000// +build !go1.2 package robustsession import ( "net" "time" ) func setupKeepAlive(conn net.Conn, period time.Duration) { } robustirc-bridge-1.8/robustsession/keepalive_go1.2.go000066400000000000000000000007601340352452700227640ustar00rootroot00000000000000// +build go1.2 // While SetKeepAlive is available in go1, the default keepalive // interval of most Linux distributions is 2h, which is not useful for // our case. So we also need SetKeepAlivePeriod, which was introduced in // go1.2, see https://github.com/golang/go/commit/918922cf package robustsession import ( "net" "time" ) func setupKeepAlive(conn net.Conn, period time.Duration) { if tc, ok := conn.(*net.TCPConn); ok { tc.SetKeepAlive(true) tc.SetKeepAlivePeriod(period) } } robustirc-bridge-1.8/robustsession/robustsession.go000066400000000000000000000327071340352452700230410ustar00rootroot00000000000000// robustsession represents a RobustIRC session and handles all communication // to the RobustIRC network. package robustsession import ( "bytes" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "hash/fnv" "io/ioutil" "log" "math" "math/rand" "net" "net/http" "net/url" "strings" "sync" "time" "github.com/sorcix/irc" ) const ( pathCreateSession = "/robustirc/v1/session" pathDeleteSession = "/robustirc/v1/%s" pathPostMessage = "/robustirc/v1/%s/message" pathGetMessages = "/robustirc/v1/%s/messages?lastseen=%s" ) const Version = "RobustIRC Bridge v1.8" type robustId struct { Id int64 Reply int64 } func (i *robustId) String() string { return fmt.Sprintf("%d.%d", i.Id, i.Reply) } type robustType int64 const ( robustCreateSession = iota robustDeleteSession robustIRCFromClient robustIRCToClient robustPing ) type robustMessage struct { Id robustId Session robustId Type robustType Data string // List of all servers currently in the network. Only present when Type == RobustPing. Servers []string `json:",omitempty"` // ClientMessageId sent by client. Only present when Type == RobustIRCFromClient ClientMessageId uint64 `json:",omitempty"` } var ( NoSuchSession = errors.New("No such RobustIRC session (killed by the network?)") networks = make(map[string]*network) networksMu sync.Mutex ) type backoffState struct { exp float64 next time.Time } type network struct { servers []string mu sync.RWMutex backoff map[string]backoffState } func newNetwork(networkname string) (*network, error) { var servers []string parts := strings.Split(networkname, ",") if len(parts) > 1 { log.Printf("Interpreting %q as list of servers instead of network name\n", networkname) servers = parts } else { // Try to resolve the DNS name up to 5 times. This is to be nice to // people in environments with flaky network connections at boot, who, // for some reason, don’t run this program under systemd with // Restart=on-failure. try := 0 for { _, addrs, err := net.LookupSRV("robustirc", "tcp", networkname) if err != nil { log.Println(err) if try < 4 { time.Sleep(time.Duration(int64(math.Pow(2, float64(try)))) * time.Second) } else { return nil, fmt.Errorf("DNS lookup of %q failed 5 times", networkname) } try++ continue } // Randomly shuffle the addresses. for i := range addrs { j := rand.Intn(i + 1) addrs[i], addrs[j] = addrs[j], addrs[i] } for _, addr := range addrs { target := addr.Target if target[len(target)-1] == '.' { target = target[:len(target)-1] } servers = append(servers, fmt.Sprintf("%s:%d", target, addr.Port)) } break } } return &network{ servers: servers, backoff: make(map[string]backoffState), }, nil } // server (eventually) returns the host:port to which we should connect to. In // case back-off prevents us from connecting anywhere right now, the function // blocks until back-off is over. func (n *network) server(random bool) string { n.mu.RLock() defer n.mu.RUnlock() for { soonest := time.Duration(math.MaxInt64) // Try to use a random server, but fall back to using the next // available server in case the randomly picked server is unhealthy. if random { server := n.servers[rand.Intn(len(n.servers))] wait := n.backoff[server].next.Sub(time.Now()) if wait <= 0 { return server } } for _, server := range n.servers { wait := n.backoff[server].next.Sub(time.Now()) if wait <= 0 { return server } if wait < soonest { soonest = wait } } time.Sleep(soonest) } // Unreached, but necessary for compiling with go1.0.2 (debian stable). return "" } func (n *network) setServers(servers []string) { n.mu.Lock() defer n.mu.Unlock() // TODO(secure): we should clean up n.backoff from servers which no longer exist n.servers = servers } // prefer adds the specified server to the front of the servers list, thereby // trying to prefer it over other servers for the next request. Note that // exponential backoff overrides this, so this is only a hint, not a guarantee. func (n *network) prefer(server string) { n.mu.Lock() defer n.mu.Unlock() n.servers = append([]string{server}, n.servers...) } func (n *network) failed(server string) { n.mu.Lock() defer n.mu.Unlock() b := n.backoff[server] // Cap the exponential backoff at 2^6 = 64 seconds. In that region, we run // into danger of the client disconnecting due to ping timeout. if b.exp < 6 { b.exp++ } b.next = time.Now().Add(time.Duration(math.Pow(2, b.exp)) * time.Second) n.backoff[server] = b } func (n *network) succeeded(server string) { n.mu.Lock() defer n.mu.Unlock() delete(n.backoff, server) } func discardResponse(resp *http.Response) { // We need to read the entire body, otherwise net/http will not // re-use this connection. ioutil.ReadAll(resp.Body) resp.Body.Close() } type RobustSession struct { IrcPrefix *irc.Prefix Messages chan string Errors chan error sessionId string // ForwardedFor will be sent in all HTTP requests as X-Forwarded-For header // if non-empty. ForwardedFor string // BridgeAuth will be sent in all HTTP requests as X-Bridge-Auth header if // non-empty. See https://github.com/robustirc/robustirc/issues/122 BridgeAuth string sessionAuth string deleted bool done chan bool network *network client *http.Client sendingMu *sync.Mutex } func (s *RobustSession) sendRequest(method, path string, data []byte) (string, *http.Response, error) { for !s.deleted { // GET requests are for read-only state and can be answered by any server. target := s.network.server(method == "GET") requrl := fmt.Sprintf("https://%s%s", target, path) req, err := http.NewRequest(method, requrl, bytes.NewBuffer(data)) if err != nil { return "", nil, err } req.Header.Set("User-Agent", Version) req.Header.Set("X-Session-Auth", s.sessionAuth) if s.ForwardedFor != "" { req.Header.Set("X-Forwarded-For", s.ForwardedFor) } if s.BridgeAuth != "" { req.Header.Set("X-Bridge-Auth", s.BridgeAuth) } req.Header.Set("Content-Type", "application/json") resp, err := s.client.Do(req) if err != nil { s.network.failed(target) log.Printf("Warning: %s: %v (trying different server)\n", requrl, err) continue } if resp.StatusCode == http.StatusOK { if cl := resp.Header.Get("Content-Location"); cl != "" { if location, err := url.Parse(cl); err == nil { s.network.prefer(location.Host) } } return target, resp, nil } message, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() s.network.failed(target) if resp.StatusCode == http.StatusNotFound { return "", nil, fmt.Errorf("Error: %s: %v (non-recoverable)\n", requrl, NoSuchSession) } // Server errors, temporary. if resp.StatusCode >= 500 && resp.StatusCode < 600 { log.Printf("Warning: %s: %v: %q (trying different server)\n", requrl, resp.Status, message) continue } // Client errors and anything unexpected, assumed to be permanent. return "", nil, fmt.Errorf("Error: %s: %v: %q (non-recoverable)\n", requrl, resp.Status, message) } return "", nil, NoSuchSession } // Create creates a new RobustIRC session. It resolves the given network name // (e.g. "robustirc.net") to a set of servers by querying the // _robustirc._tcp. SRV record and sends the CreateSession request. // // When err == nil, the caller MUST read the RobustSession.Messages and // RobustSession.Errors channels. // // tlsCAFile specifies the path to an x509 root certificate, which is mostly // useful for testing. If empty, the system CA store will be used // (recommended). func Create(network string, tlsCAFile string) (*RobustSession, error) { networksMu.Lock() n, ok := networks[network] if !ok { var err error n, err = newNetwork(network) if err != nil { networksMu.Unlock() return nil, err } networks[network] = n } networksMu.Unlock() var tlsConfig *tls.Config if tlsCAFile != "" { roots := x509.NewCertPool() contents, err := ioutil.ReadFile(tlsCAFile) if err != nil { log.Fatalf("Could not read cert.pem: %v", err) } if !roots.AppendCertsFromPEM(contents) { log.Fatalf("Could not parse %q", tlsCAFile) } tlsConfig = &tls.Config{RootCAs: roots} } // This is copied from net/http.DefaultTransport as of go1.4. transport := &http.Transport{ // The 70s timeout has been chosen such that: // 1) It is higher than the interval with which the server sends pings // to us (20s). // 2) It is higher than the interval with which we send pings to the // server (60s) so that the connections can be re-used (HTTP // keepalive). Dial: DeadlineConnDialer(5*time.Second, 30*time.Second, 70*time.Second), TLSClientConfig: tlsConfig, Proxy: http.ProxyFromEnvironment, MaxIdleConnsPerHost: 1, } setupTLSHandshakeTimeout(transport, 10*time.Second) client := &http.Client{Transport: transport} s := &RobustSession{ Messages: make(chan string), Errors: make(chan error), done: make(chan bool), network: n, client: client, sendingMu: &sync.Mutex{}, } _, resp, err := s.sendRequest("POST", pathCreateSession, nil) if err != nil { return nil, err } defer discardResponse(resp) var createSessionReply struct { Sessionid string Sessionauth string Prefix string } if err := json.NewDecoder(resp.Body).Decode(&createSessionReply); err != nil { return nil, err } s.sessionId = createSessionReply.Sessionid s.sessionAuth = createSessionReply.Sessionauth s.IrcPrefix = &irc.Prefix{Name: createSessionReply.Prefix} go s.getMessages() return s, nil } func (s *RobustSession) getMessages() { var lastseen robustId defer func() { for _ = range s.done { } }() for !s.deleted { target, resp, err := s.sendRequest("GET", fmt.Sprintf(pathGetMessages, s.sessionId, lastseen.String()), nil) if err != nil { s.Errors <- err return } dec := json.NewDecoder(resp.Body) for !s.deleted { var msg robustMessage if err := dec.Decode(&msg); err != nil { if !s.deleted { log.Printf("Protocol error on %q: Could not decode response chunk as JSON: %v\n", target, err) } s.network.failed(target) break } if msg.Type == robustPing { s.network.setServers(msg.Servers) } else if msg.Type == robustIRCToClient { s.Messages <- msg.Data lastseen = msg.Id } } // Cannot use discardResponse() because the response never completes. resp.Body.Close() // Delay reconnecting for somewhere in between [250, 500) ms to avoid // overloading the remaining servers from many clients at once when one // server fails. time.Sleep(time.Duration(250+rand.Int63n(250)) * time.Millisecond) } } // SessionId returns a string that identifies the session. It should be used in // log messages to identify sessions. func (s *RobustSession) SessionId() string { return s.sessionId } // PostMessage posts the given IRC message. It will retry automatically on // transient errors, and only return an error when the network returned a // permanent error, such as NoSuchSession. // // The RobustIRC protocol dictates that you must not try to send more than one // message at any given point in time, and PostMessage enforces this by using a // mutex. func (s *RobustSession) PostMessage(message string) error { s.sendingMu.Lock() defer s.sendingMu.Unlock() type postMessageRequest struct { Data string ClientMessageId uint64 } h := fnv.New32() h.Write([]byte(message)) // The message id should be unique across separate instances of the bridge, // even if they were attached to the same session. A collision in this case // means one bridge instance (with the same session) is unable to send a // message because the message id is equal to the one the other bridge // instance just sent. With the hash of the message itself, such a // collision can only occur when both instances try to send exactly the // same message _and_ the random value is the same for both instances. msgid := (uint64(h.Sum32()) << 32) | uint64(rand.Int31n(math.MaxInt32)) b, err := json.Marshal(postMessageRequest{ Data: message, ClientMessageId: msgid, }) if err != nil { return fmt.Errorf("Message could not be encoded as JSON: %v\n", err) } target, resp, err := s.sendRequest("POST", fmt.Sprintf(pathPostMessage, s.sessionId), b) if err != nil { return err } discardResponse(resp) s.network.succeeded(target) return nil } // Delete sends a delete request for this session on the server. // // This session MUST not be used after this method returns. Even if the delete // request did not succeed, the session is deleted from the client’s point of // view. func (s *RobustSession) Delete(quitmessage string) error { defer func() { s.deleted = true // Make sure nobody is blocked on sending to the channel by closing them // and reading all remaining values. go func() { for _ = range s.Messages { } }() go func() { for _ = range s.Errors { } }() // This will be read by getMessages(), which will not send on the // channels after reading it, so we can safely close them. s.done <- true close(s.done) close(s.Messages) close(s.Errors) if transport, ok := s.client.Transport.(*http.Transport); ok { transport.CloseIdleConnections() } }() b, err := json.Marshal(struct{ Quitmessage string }{quitmessage}) if err != nil { return err } _, resp, err := s.sendRequest("DELETE", fmt.Sprintf(pathDeleteSession, s.sessionId), b) if err != nil { return err } discardResponse(resp) return nil } robustirc-bridge-1.8/robustsession/timeout_dummy.go000066400000000000000000000002351340352452700230070ustar00rootroot00000000000000// +build !go1.3 package robustsession import ( "net/http" "time" ) func setupTLSHandshakeTimeout(transport *http.Transport, timeout time.Duration) { } robustirc-bridge-1.8/robustsession/timeout_go1.3.go000066400000000000000000000004521340352452700225040ustar00rootroot00000000000000// +build go1.3 // TLSHandshakeTimeout was introduced in go1.3, see // https://github.com/golang/go/commit/fd4b4b56 package robustsession import ( "net/http" "time" ) func setupTLSHandshakeTimeout(transport *http.Transport, timeout time.Duration) { transport.TLSHandshakeTimeout = timeout } robustirc-bridge-1.8/tlsutil/000077500000000000000000000000001340352452700163355ustar00rootroot00000000000000robustirc-bridge-1.8/tlsutil/tlsutil.go000066400000000000000000000025121340352452700203640ustar00rootroot00000000000000// +build go1.4 package tlsutil import ( "crypto/tls" "log" "os" "os/signal" "sync" "syscall" ) type keypairReloader struct { certMu sync.RWMutex cert *tls.Certificate certPath string keyPath string } func NewKeypairReloader(certPath, keyPath string) (*keypairReloader, error) { result := &keypairReloader{ certPath: certPath, keyPath: keyPath, } cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, err } result.cert = &cert go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGHUP) for _ = range c { log.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q", certPath, keyPath) if err := result.maybeReload(); err != nil { log.Printf("Keeping old TLS certificate because the new one could not be loaded: %v", err) } } }() return result, nil } func (kpr *keypairReloader) maybeReload() error { newCert, err := tls.LoadX509KeyPair(kpr.certPath, kpr.keyPath) if err != nil { return err } kpr.certMu.Lock() defer kpr.certMu.Unlock() kpr.cert = &newCert return nil } func (kpr *keypairReloader) GetCertificateFunc() func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { kpr.certMu.RLock() defer kpr.certMu.RUnlock() return kpr.cert, nil } }