package server import ( "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "errors" "io/ioutil" "log" "net" "net/http" "net/http/httputil" "net/rpc" "os" "path/filepath" "strings" "time" "git.xeserv.us/xena/route/database" "git.xeserv.us/xena/route/lib/elfs" "git.xeserv.us/xena/route/lib/tun2" "git.xeserv.us/xena/route/routerpc" "git.xeserv.us/xena/route/utils" "github.com/Xe/uuid" "github.com/Yawning/bulb" "github.com/brandur/simplebox" "github.com/mtneug/pkg/ulid" "golang.org/x/crypto/acme/autocert" ) // RPC constants const ( RPCPort uint16 = 39453 ) // Server is the main server type type Server struct { cfg *Config db *database.DB tor *Tor rpcS *rpc.Server rpcAddr string ts *tun2.Server CertCache *database.CertCache } // Config configures Server type Config struct { ControlHost, ControlKeyFile string RethinkDBHost, RethinkDBDatabase string TorDataDir, TorHashedPassword, TorPassword string WebPort, DomainSuffix, SSLPort, GRPCClientPort string BackendPort, KCPPort string CertKey *[32]byte } // New creates a new Server func New(cfg Config) (*Server, error) { db, err := database.New(cfg.RethinkDBHost, cfg.RethinkDBDatabase) if err != nil { return nil, err } l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, err } t, err := StartTor(TorConfig{ DataDir: cfg.TorDataDir, HashedControlPassword: cfg.TorHashedPassword, ClearPassword: cfg.TorPassword, Timeout: 30 * time.Second, }) if err != nil { return nil, err } fin, err := os.Open(cfg.ControlKeyFile) if err != nil { return nil, err } defer fin.Close() data, err := ioutil.ReadAll(fin) if err != nil { return nil, err } pKey, err := utils.PemToRSAPrivateKey(data) if err != nil { return nil, err } _, err = t.AddOnion(pKey, RPCPort, l.Addr().String()) if err != nil { return nil, err } rpcs := rpc.NewServer() s := &Server{ cfg: &cfg, db: db, tor: t, rpcS: rpcs, rpcAddr: l.Addr().String(), CertCache: &database.CertCache{ DB: db, }, } m := autocert.Manager{ Prompt: autocert.AcceptTOS, Cache: s.CertCache, HostPolicy: nil, Email: "xena@yolo-swag.com", } if cfg.CertKey != nil { s.CertCache.SimpleBox = simplebox.NewFromSecretKey(cfg.CertKey) } tcfg := &tun2.ServerConfig{ TCPAddr: cfg.BackendPort, KCPAddr: cfg.KCPPort, TLSConfig: &tls.Config{ GetCertificate: m.GetCertificate, }, Storage: s.db, } ts, err := tun2.NewServer(tcfg) if err != nil { return nil, err } s.ts = ts rpcs.RegisterName("Urls", &RPCServer{Server: s}) go rpcs.Accept(l) log.Println("rpc at tcp://" + l.Addr().String()) err = s.restore() if err != nil { return nil, err } return s, nil } func (s *Server) onionPath(name string) string { return filepath.Join(s.cfg.TorDataDir, name) } func (s *Server) Director(r *http.Request) { } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.Header.Del("X-Forwarded-For") if r.Header.Get("X-Tor2web") != "" { http.Error(w, "tor2web proxy use is not allowed", 400) return } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { http.Error(w, err.Error(), 500) return } r.Header.Set("X-Forwarded-For", host) r.Header.Set("X-Remote-IP", host) r.Header.Set("X-Request-Ingress", time.Now().String()) rid := ulid.New().String() r.Header.Set("X-Request-Id", rid) w.Header().Set("X-Request-Id", rid) // http://www.gnuterrypratchett.com/ w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn") if strings.HasSuffix(r.Host, ".onion") { r.Header.Add("DNT", "1") } if r.RequestURI == rpc.DefaultRPCPath { s.rpcS.ServeHTTP(w, r) return } rp := &httputil.ReverseProxy{ Director: s.Director, Transport: s.ts, FlushInterval: 1 * time.Second, } rp.ServeHTTP(w, r) } func (s *Server) restore() error { rts, err := s.db.GetAllRoutes() if err != nil { return err } for _, rt := range rts { var block *pem.Block block, _ = pem.Decode([]byte(rt.OnionKey)) pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return err } _, err = s.tor.AddOnion(pKey, 80, "127.0.0.1:"+s.cfg.WebPort) if err != nil { return err } log.Printf("added: %s (%s)", rt.Hostname, rt.OnionHostname) } return nil } func genPortSpec(incoming uint16, outgoing string) bulb.OnionPortSpec { return bulb.OnionPortSpec{ VirtPort: incoming, Target: outgoing, } } // RPCServer is a Server wrapped to work inside the context of net/rpc type RPCServer struct { *Server } // AddHost adds a host to the server. func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error { if req.APIKey != "hunter2" { return errors.New("invalid api key") } token := uuid.New() var pKey *rsa.PrivateKey if kk, ok := req.PrivKey.(*rsa.PrivateKey); ok { pKey = kk } else { return errors.New("there must be a 1024 bit RSA private key") } oi, err := rs.tor.AddOnion(pKey, 80, "127.0.0.1:"+rs.cfg.WebPort) if err != nil { return err } resp.OnionHostname = oi.OnionID + ".onion" resp.Token = token resp.PrivKey = pKey if req.Hostname != "" { resp.Hostname = req.Hostname } else { resp.Hostname = elfs.MakeName() + rs.cfg.DomainSuffix } err = rs.db.SaveRoute(resp) if err != nil { return err } return nil }