/* * Copyright (c) 2014, Yawning Angel * 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. * * 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 HOLDER 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. */ // Go language Tor Pluggable Transport suite. Works only as a managed // client/server. package main import ( "flag" "fmt" "io" "io/ioutil" "log" "net" "net/url" "os" "os/signal" "path" "sync" "syscall" "golang.org/x/net/proxy" "git.torproject.org/pluggable-transports/goptlib.git" "git.torproject.org/pluggable-transports/obfs4.git/transports" "git.torproject.org/pluggable-transports/obfs4.git/transports/base" ) const ( obfs4proxyVersion = "0.0.4" obfs4proxyLogFile = "obfs4proxy.log" socksAddr = "127.0.0.1:0" elidedAddr = "[scrubbed]" ) var enableLogging bool var unsafeLogging bool var stateDir string var handlerChan chan int // DialFn is a function pointer to a function that matches the net.Dialer.Dial // interface. type DialFn func(string, string) (net.Conn, error) func elideAddr(addrStr string) string { if unsafeLogging { return addrStr } if addr, err := resolveAddrStr(addrStr); err == nil { // Only scrub off the address so that it's slightly easier to follow // the logs by looking at the port. return fmt.Sprintf("%s:%d", elidedAddr, addr.Port) } return elidedAddr } func elideError(err error) string { // Go's net package is somewhat rude and includes IP address and port // information in the string representation of net.Errors. Figure out if // this is the case here, and sanitize the error messages as needed. if unsafeLogging { return err.Error() } // If err is not a net.Error, just return the string representation, // presumably transport authors know what they are doing. netErr, ok := err.(net.Error) if !ok { return err.Error() } switch t := netErr.(type) { case *net.AddrError: return t.Err + " " + elidedAddr case *net.DNSError: return "lookup " + elidedAddr + " on " + elidedAddr + ": " + t.Err case *net.InvalidAddrError: return "invalid address error" case *net.UnknownNetworkError: return "unknown network " + elidedAddr case *net.OpError: return t.Op + ": " + t.Err.Error() default: // For unknown error types, do the conservative thing and only log the // type of the error instead of assuming that the string representation // does not contain sensitive information. return fmt.Sprintf("network error: <%T>", t) } } func clientSetup() (launched bool, listeners []net.Listener) { ptClientInfo, err := pt.ClientSetup(transports.Transports()) if err != nil { log.Fatal(err) } ptClientProxy, err := ptGetProxy() if err != nil { log.Fatal(err) } else if ptClientProxy != nil { ptProxyDone() } // Launch each of the client listeners. for _, name := range ptClientInfo.MethodNames { t := transports.Get(name) if t == nil { pt.CmethodError(name, "no such transport is supported") continue } f, err := t.ClientFactory(stateDir) if err != nil { pt.CmethodError(name, "failed to get ClientFactory") continue } ln, err := pt.ListenSocks("tcp", socksAddr) if err != nil { pt.CmethodError(name, err.Error()) continue } go clientAcceptLoop(f, ln, ptClientProxy) pt.Cmethod(name, ln.Version(), ln.Addr()) infof("%s - registered listener: %s", name, ln.Addr()) listeners = append(listeners, ln) launched = true } pt.CmethodsDone() return } func clientAcceptLoop(f base.ClientFactory, ln *pt.SocksListener, proxyURI *url.URL) error { defer ln.Close() for { conn, err := ln.AcceptSocks() if err != nil { if e, ok := err.(net.Error); ok && !e.Temporary() { return err } continue } go clientHandler(f, conn, proxyURI) } } func clientHandler(f base.ClientFactory, conn *pt.SocksConn, proxyURI *url.URL) { defer conn.Close() handlerChan <- 1 defer func() { handlerChan <- -1 }() name := f.Transport().Name() addrStr := elideAddr(conn.Req.Target) infof("%s(%s) - new connection", name, addrStr) // Deal with arguments. args, err := f.ParseArgs(&conn.Req.Args) if err != nil { errorf("%s(%s) - invalid arguments: %s", name, addrStr, err) conn.Reject() return } // Obtain the proxy dialer if any, and create the outgoing TCP connection. var dialFn DialFn if proxyURI == nil { dialFn = proxy.Direct.Dial } else { // This is unlikely to happen as the proxy protocol is verified during // the configuration phase. dialer, err := proxy.FromURL(proxyURI, proxy.Direct) if err != nil { errorf("%s(%s) - failed to obtain proxy dialer: %s", name, addrStr, elideError(err)) conn.Reject() return } dialFn = dialer.Dial } remoteConn, err := dialFn("tcp", conn.Req.Target) // XXX: Allow UDP? if err != nil { errorf("%s(%s) - outgoing connection failed: %s", name, addrStr, elideError(err)) conn.Reject() return } defer remoteConn.Close() // Instantiate the client transport method, handshake, and start pushing // bytes back and forth. remote, err := f.WrapConn(remoteConn, args) if err != nil { errorf("%s(%s) - handshake failed: %s", name, addrStr, elideError(err)) conn.Reject() return } err = conn.Grant(remoteConn.RemoteAddr().(*net.TCPAddr)) if err != nil { errorf("%s(%s) - SOCKS grant failed: %s", name, addrStr, elideError(err)) return } if err = copyLoop(conn, remote); err != nil { warnf("%s(%s) - closed connection: %s", name, addrStr, elideError(err)) } else { infof("%s(%s) - closed connection", name, addrStr) } return } func serverSetup() (launched bool, listeners []net.Listener) { ptServerInfo, err := pt.ServerSetup(transports.Transports()) if err != nil { log.Fatal(err) } for _, bindaddr := range ptServerInfo.Bindaddrs { name := bindaddr.MethodName t := transports.Get(name) if t == nil { pt.SmethodError(name, "no such transport is supported") continue } f, err := t.ServerFactory(stateDir, &bindaddr.Options) if err != nil { pt.SmethodError(name, err.Error()) continue } ln, err := net.ListenTCP("tcp", bindaddr.Addr) if err != nil { pt.SmethodError(name, err.Error()) continue } go serverAcceptLoop(f, ln, &ptServerInfo) if args := f.Args(); args != nil { pt.SmethodArgs(name, ln.Addr(), *args) } else { pt.SmethodArgs(name, ln.Addr(), nil) } infof("%s - registered listener: %s", name, elideAddr(ln.Addr().String())) listeners = append(listeners, ln) launched = true } pt.SmethodsDone() return } func serverAcceptLoop(f base.ServerFactory, ln net.Listener, info *pt.ServerInfo) error { defer ln.Close() for { conn, err := ln.Accept() if err != nil { if e, ok := err.(net.Error); ok && !e.Temporary() { return err } continue } go serverHandler(f, conn, info) } } func serverHandler(f base.ServerFactory, conn net.Conn, info *pt.ServerInfo) { defer conn.Close() handlerChan <- 1 defer func() { handlerChan <- -1 }() name := f.Transport().Name() addrStr := elideAddr(conn.RemoteAddr().String()) infof("%s(%s) - new connection", name, addrStr) // Instantiate the server transport method and handshake. remote, err := f.WrapConn(conn) if err != nil { warnf("%s(%s) - handshake failed: %s", name, addrStr, elideError(err)) return } // Connect to the orport. orConn, err := pt.DialOr(info, conn.RemoteAddr().String(), name) if err != nil { errorf("%s(%s) - failed to connect to ORPort: %s", name, addrStr, elideError(err)) return } defer orConn.Close() if err = copyLoop(orConn, remote); err != nil { warnf("%s(%s) - closed connection: %s", name, addrStr, elideError(err)) } else { infof("%s(%s) - closed connection", name, addrStr) } return } func copyLoop(a net.Conn, b net.Conn) error { // Note: b is always the pt connection. a is the SOCKS/ORPort connection. errChan := make(chan error, 2) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() defer b.Close() defer a.Close() _, err := io.Copy(b, a) errChan <- err }() go func() { defer wg.Done() defer a.Close() defer b.Close() _, err := io.Copy(a, b) errChan <- err }() // Wait for both upstream and downstream to close. Since one side // terminating closes the other, the second error in the channel will be // something like EINVAL (though io.Copy() will swallow EOF), so only the // first error is returned. wg.Wait() if len(errChan) > 0 { return <-errChan } return nil } func ptInitializeLogging(enable bool) error { if enable { // While we could just exit, log an ENV-ERROR so it will propagate to // the tor log. f, err := os.OpenFile(path.Join(stateDir, obfs4proxyLogFile), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { return ptEnvError(fmt.Sprintf("failed to open log file: %s\n", err)) } log.SetOutput(f) } else { log.SetOutput(ioutil.Discard) } return nil } func getVersion() string { return fmt.Sprintf("obfs4proxy-%s", obfs4proxyVersion) } func main() { // Handle the command line arguments. _, execName := path.Split(os.Args[0]) showVer := flag.Bool("version", false, "Print version and exit") logLevelStr := flag.String("logLevel", "ERROR", "Log level (ERROR/WARN/INFO)") flag.BoolVar(&enableLogging, "enableLogging", false, "Log to TOR_PT_STATE_LOCATION/"+obfs4proxyLogFile) flag.BoolVar(&unsafeLogging, "unsafeLogging", false, "Disable the address scrubber") flag.Parse() if *showVer { fmt.Printf("%s\n", getVersion()) os.Exit(0) } if err := setLogLevel(*logLevelStr); err != nil { log.Fatalf("[ERROR]: failed to set log level: %s", err) } // Determine if this is a client or server, initialize logging, and finish // the pt configuration. var ptListeners []net.Listener handlerChan = make(chan int) launched := false isClient, err := ptIsClient() if err != nil { log.Fatalf("[ERROR]: %s - must be run as a managed transport", execName) } if stateDir, err = pt.MakeStateDir(); err != nil { log.Fatalf("[ERROR]: %s - No state directory: %s", execName, err) } if err = ptInitializeLogging(enableLogging); err != nil { log.Fatalf("[ERROR]: %s - failed to initialize logging", execName) } else { noticef("%s - launched", getVersion()) } if isClient { infof("%s - initializing client transport listeners", execName) launched, ptListeners = clientSetup() } else { infof("%s - initializing server transport listeners", execName) launched, ptListeners = serverSetup() } if !launched { // Initialization failed, the client or server setup routines should // have logged, so just exit here. os.Exit(-1) } infof("%s - accepting connections", execName) defer func() { noticef("%s - terminated", execName) }() // At this point, the pt config protocol is finished, and incoming // connections will be processed. Per the pt spec, on sane platforms // termination is signaled via SIGINT (or SIGTERM), so wait on tor to // request a shutdown of some sort. sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // Wait for the first SIGINT (close listeners). var sig os.Signal numHandlers := 0 for sig == nil { select { case n := <-handlerChan: numHandlers += n case sig = <-sigChan: if sig == syscall.SIGTERM { // SIGTERM causes immediate termination. return } } } for _, ln := range ptListeners { ln.Close() } // Wait for the 2nd SIGINT (or a SIGTERM), or for all current sessions to // finish. sig = nil for sig == nil && numHandlers != 0 { select { case n := <-handlerChan: numHandlers += n case sig = <-sigChan: } } }