From f7df55f0442cdf01f04d1cd3eb15f8ccecd7e4d8 Mon Sep 17 00:00:00 2001 From: Cadey Dodrill Date: Wed, 18 Jan 2017 09:02:44 -0800 Subject: [PATCH] Move things around, set up a more structured app --- README.md | 10 + database/db.go | 80 +++++++ lib/elfs/elfs.go | 505 ++++++++++++++++++++++++++++++++++++++++++++ lib/elfs/moves.moon | 225 ++++++++++++++++++++ lib/elfs/names.moon | 250 ++++++++++++++++++++++ main.go | 206 +----------------- server/server.go | 263 +++++++++++++++++++++++ 7 files changed, 1339 insertions(+), 200 deletions(-) create mode 100644 README.md create mode 100644 database/db.go create mode 100644 lib/elfs/elfs.go create mode 100644 lib/elfs/moves.moon create mode 100644 lib/elfs/names.moon create mode 100644 server/server.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..8db56d1 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +route +===== + +A simple cluster-friendly load balancer for singleton microservices. + +TODO +---- + +- [ ] Store stuff into RethinkDB +- [ ] Add Let's Encrypt support in addition to a free tor hidden service with every domain routed diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..b6609ab --- /dev/null +++ b/database/db.go @@ -0,0 +1,80 @@ +package database + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + + "git.xeserv.us/xena/route/routerpc" + r "github.com/GoRethink/gorethink" +) + +type DB struct { + s *r.Session +} + +func New(host, database string) (*DB, error) { + session, err := r.Connect(r.ConnectOpts{ + Address: host, + Database: database, + }) + if err != nil { + return nil, err + } + + db := &DB{ + s: session, + } + + return db, nil +} + +var tables = []string{ + "certs", + "routes", +} + +type Route struct { + ID string `gorethink:"id,omitempty"` + Hostname string `gorethink:"hostname"` + OnionHostname string `gorethink:"onionhostname"` + Token string `gorethink:"token"` + OnionKey []byte `gorethink:"onionKey"` // PEM-encoded +} + +func (db *DB) SaveRoute(resp *routerpc.AddHostResponse) error { + pemblock := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(resp.PrivKey.(*rsa.PrivateKey)), + } + bytes := pem.EncodeToMemory(pemblock) + + rt := &Route{ + Hostname: resp.Hostname, + OnionHostname: resp.OnionHostname, + Token: resp.Token, + OnionKey: bytes, + } + + _, err := r.Table("routes").Insert(rt).RunWrite(db.s) + if err != nil { + return err + } + + return nil +} + +func (db *DB) GetAllRoutes() ([]Route, error) { + rows, err := r.Table("routes").Run(db.s) + if err != nil { + return nil, err + } + + var routes []Route + err = rows.All(&routes) + if err != nil { + return nil, err + } + + return routes, nil +} diff --git a/lib/elfs/elfs.go b/lib/elfs/elfs.go new file mode 100644 index 0000000..7d6b5dd --- /dev/null +++ b/lib/elfs/elfs.go @@ -0,0 +1,505 @@ +/* +Package elfs is this project's heroku style name generator. +*/ +package elfs + +import ( + "fmt" + "math/rand" +) + +// Names is the name of every Pokemon from Pokemon Vietnamese Crystal. +var Names = []string{ + "SEED", + "GRASS", + "FLOWE", + "SHAD", + "CABR", + "SNAKE", + "GOLD", + "COW", + "GUIKI", + "PEDAL", + "DELAN", + "B-FLY", + "BIDE", + "KEYU", + "FORK", + "LAP", + "PIGE", + "PIJIA", + "CAML", + "LAT", + "BIRD", + "BABOO", + "VIV", + "ABOKE", + "PIKAQ", + "RYE", + "SAN", + "BREAD", + "LIDEL", + "LIDE", + "PIP", + "PIKEX", + "ROK", + "JUGEN", + "PUD", + "BUDE", + "ZHIB", + "GELU", + "GRAS", + "FLOW", + "LAFUL", + "ATH", + "BALA", + "CORN", + "MOLUF", + "DESP", + "DAKED", + "MIMI", + "BOLUX", + "KODA", + "GELUD", + "MONK", + "SUMOY", + "GEDI", + "WENDI", + "NILEM", + "NILE", + "NILEC", + "KEZI", + "YONGL", + "HUDE", + "WANLI", + "GELI", + "GUAIL", + "MADAQ", + "WUCI", + "WUCI", + "MUJEF", + "JELLY", + "SICIB", + "GELU", + "NELUO", + "BOLI", + "JIALE", + "YED", + "YEDE", + "CLO", + "SCARE", + "AOCO", + "DEDE", + "DEDEI", + "BAWU", + "JIUG", + "BADEB", + "BADEB", + "HOLE", + "BALUX", + "GES", + "FANT", + "QUAR", + "YIHE", + "SWAB", + "SLIPP", + "CLU", + "DEPOS", + "BILIY", + "YUANO", + "SOME", + "NO", + "YELA", + "EMPT", + "ZECUN", + "XIAHE", + "BOLEL", + "DEJI", + "MACID", + "XIHON", + "XITO", + "LUCK", + "MENJI", + "GELU", + "DECI", + "XIDE", + "DASAJ", + "DONGN", + "RICUL", + "MINXI", + "BALIY", + "ZENDA", + "LUZEL", + "HELE5", + "0FENB", + "KAIL", + "JIAND", + "CARP", + "JINDE", + "LAPU", + "MUDE", + "YIFU", + "LINLI", + "SANDI", + "HUSI", + "JINC", + "OUMU", + "OUMUX", + "CAP", + "KUIZA", + "PUD", + "TIAO", + "FRMAN", + "CLAU", + "SPARK", + "DRAGO", + "BOLIU", + "GUAIL", + "MIYOU", + "MIY", + "QIAOK", + "BEIL", + "MUKEI", + "RIDED", + "MADAM", + "BAGEP", + "CROC", + "ALIGE", + "OUDAL", + "OUD", + "DADA", + "HEHE", + "YEDEA", + "NUXI", + "NUXIN", + "ROUY", + "ALIAD", + "STICK", + "QIANG", + "LAAND", + "PIQI", + "PI", + "PUPI", + "DEKE", + "DEKEJ", + "NADI", + "NADIO", + "MALI", + "PEA", + "ELECT", + "FLOWE", + "MAL", + "MALI", + "HUSHU", + "NILEE", + "YUZI", + "POPOZ", + "DUZI", + "HEBA", + "XIAN", + "SHAN", + "YEYEA", + "WUY", + "LUO", + "KEFE", + "HULA", + "CROW", + "YADEH", + "MOW", + "ANNAN", + "SUONI", + "KYLI", + "HULU", + "HUDEL", + "YEHE", + "GULAE", + "YEHE", + "BLU", + "GELAN", + "BOAT", + "NIP", + "POIT", + "HELAK", + "XINL", + "BEAR", + "LINB", + "MAGEH", + "MAGEJ", + "WULI", + "YIDE", + "RIVE", + "FISH", + "AOGU", + "DELIE", + "MANTE", + "KONMU", + "DELU", + "HELU", + "HUAN", + "HUMA", + "DONGF", + "JINCA", + "HEDE", + "DEFU", + "LIBY", + "JIAPA", + "MEJI", + "HELE", + "BUHU", + "MILK", + "HABI", + "THUN", + "GARD", + "DON", + "YANGQ", + "SANAQ", + "BANQ", + "LUJ", + "PHIX", + "SIEI", + "EGG", +} + +// Moves is every single move from Pokemon Vietnamese Crystal. +var Moves = []string{ + "ABLE", + "ABNORMA", + "AGAIN", + "AIREXPL", + "ANG", + "ANGER", + "ASAIL", + "ATTACK", + "AURORA", + "AWL", + "BAN", + "BAND", + "BARE", + "BEAT", + "BEATED", + "BELLY", + "BIND", + "BITE", + "BLOC", + "BLOOD", + "BODY", + "BOOK", + "BREATH", + "BUMP", + "CAST", + "CHAM", + "CLAMP", + "CLAP", + "CLAW", + "CLEAR", + "CLI", + "CLIP", + "CLOUD", + "CONTRO", + "CONVY", + "COOLHIT", + "CRASH", + "CRY", + "CUT", + "DESCRI", + "D-FIGHT", + "DIG", + "DITCH", + "DIV", + "DOZ", + "DRE", + "DUL", + "DU-PIN", + "DYE", + "EARTH", + "EDU", + "EG-BOMB", + "EGG", + "ELEGY", + "ELE-HIT", + "EMBODY", + "EMPLI", + "ENGL", + "ERUPT", + "EVENS", + "EXPLOR", + "EYES", + "FALL", + "FAST", + "F-CAR", + "F-DANCE", + "FEARS", + "F-FIGHT", + "FIGHT", + "FIR", + "FIRE", + "FIREHIT", + "FLAME", + "FLAP", + "FLASH", + "FLEW", + "FORCE", + "FRA", + "FREEZE", + "FROG", + "G-BIRD", + "GENKISS", + "GIFT", + "G-KISS", + "G-MOUSE", + "GRADE", + "GROW", + "HAMMER", + "HARD", + "HAT", + "HATE", + "H-BOMB", + "HELL-R", + "HEMP", + "HINT", + "HIT", + "HU", + "HUNT", + "HYPNOSI", + "INHA", + "IRO", + "IRONBAR", + "IR-WING", + "J-GUN", + "KEE", + "KICK", + "KNIF", + "KNIFE", + "KNOCK", + "LEVEL", + "LIGH", + "LIGHHIT", + "LIGHT", + "LIVE", + "L-WALL", + "MAD", + "MAJUS", + "MEL", + "MELO", + "MESS", + "MILK", + "MIMI", + "MISS", + "MIXING", + "MOVE", + "MUD", + "NI-BED", + "NOISY", + "NOONLI", + "NULL", + "N-WAVE", + "PAT", + "PEACE", + "PIN", + "PLAN", + "PLANE", + "POIS", + "POL", + "POWDE", + "POWE", + "POWER", + "PRIZE", + "PROTECT", + "PROUD", + "RAGE", + "RECOR", + "REFLAC", + "REFREC", + "REGR", + "RELIV", + "RENEW", + "R-FIGHT", + "RING", + "RKICK", + "ROCK", + "ROUND", + "RUS", + "RUSH", + "SAND", + "SAW", + "SCISSOR", + "SCRA", + "SCRIPT", + "SEEN", + "SERVER", + "SHADOW", + "SHELL", + "SHINE", + "SHO", + "SIGHT", + "SIN", + "SMALL", + "SMELT", + "SMOK", + "SNAKE", + "SNO", + "SNOW", + "SOU", + "SO-WAVE", + "SPAR", + "SPEC", + "SPID", + "S-PIN", + "SPRA", + "STAM", + "STARE", + "STEA", + "STONE", + "STORM", + "STRU", + "STRUG", + "STUDEN", + "SUBS", + "SUCID", + "SUN-LIG", + "SUNRIS", + "SUPLY", + "S-WAVE", + "TAILS", + "TANGL", + "TASTE", + "TELLI", + "THANK", + "TONKICK", + "TOOTH", + "TORL", + "TRAIN", + "TRIKICK", + "TUNGE", + "VOLT", + "WA-GUN", + "WATCH", + "WAVE", + "W-BOMB", + "WFALL", + "WFING", + "WHIP", + "WHIRL", + "WIND", + "WOLF", + "WOOD", + "WOR", + "YUJA", +} + +func randomMove() string { + return Moves[rand.Intn(len(Moves))] +} + +func randomName() string { + return Names[rand.Intn(len(Names))] +} + +// MakeName generates a new domain name based on the moves and Pokemon names +// from Pokemon Vietnamese Crystal. +func MakeName() string { + move1 := randomMove() + move2 := randomMove() + poke := randomName() + return fmt.Sprintf("%s-%s-%s", move1, move2, poke) +} diff --git a/lib/elfs/moves.moon b/lib/elfs/moves.moon new file mode 100644 index 0000000..66d3fa8 --- /dev/null +++ b/lib/elfs/moves.moon @@ -0,0 +1,225 @@ +{ + "ABLE", + "ABNORMA", + "AGAIN", + "AIREXPL", + "ANG", + "ANGER", + "ASAIL", + "ATTACK", + "AURORA", + "AWL", + "BAN", + "BAND", + "BARE", + "BEAT", + "BEATED", + "BELLY", + "BIND", + "BITE", + "BLOC", + "BLOOD", + "BODY", + "BOOK", + "BREATH", + "BUMP", + "CAST", + "CHAM", + "CLAMP", + "CLAP", + "CLAW", + "CLEAR", + "CLI", + "CLIP", + "CLOUD", + "CONTRO", + "CONVY", + "COOLHIT", + "CRASH", + "CRY", + "CUT", + "DESCRI", + "D-FIGHT", + "DIG", + "DITCH", + "DIV", + "DOZ", + "DRE", + "DUL", + "DU-PIN", + "DYE", + "EARTH", + "EDU", + "EG-BOMB", + "EGG", + "ELEGY", + "ELE-HIT", + "EMBODY", + "EMPLI", + "ENGL", + "ERUPT", + "EVENS", + "EXPLOR", + "EYES", + "FALL", + "FAST", + "F-CAR", + "F-DANCE", + "FEARS", + "F-FIGHT", + "FIGHT", + "FIR", + "FIRE", + "FIREHIT", + "FLAME", + "FLAP", + "FLASH", + "FLEW", + "FORCE", + "FRA", + "FREEZE", + "FROG", + "G-BIRD", + "GENKISS", + "GIFT", + "G-KISS", + "G-MOUSE", + "GRADE", + "GROW", + "HAMMER", + "HARD", + "HAT", + "HATE", + "H-BOMB", + "HELL-R", + "HEMP", + "HINT", + "HIT", + "HU", + "HUNT", + "HYPNOSI", + "INHA", + "IRO", + "IRONBAR", + "IR-WING", + "J-GUN", + "KEE", + "KICK", + "KNIF", + "KNIFE", + "KNOCK", + "LEVEL", + "LIGH", + "LIGHHIT", + "LIGHT", + "LIVE", + "L-WALL", + "MAD", + "MAJUS", + "MEL", + "MELO", + "MESS", + "MILK", + "MIMI", + "MISS", + "MIXING", + "MOVE", + "MUD", + "NI-BED", + "NOISY", + "NOONLI", + "NULL", + "N-WAVE", + "PAT", + "PEACE", + "PIN", + "PLAN", + "PLANE", + "POIS", + "POL", + "POWDE", + "POWE", + "POWER", + "PRIZE", + "PROTECT", + "PROUD", + "RAGE", + "RECOR", + "REFLAC", + "REFREC", + "REGR", + "RELIV", + "RENEW", + "R-FIGHT", + "RING", + "RKICK", + "ROCK", + "ROUND", + "RUS", + "RUSH", + "SAND", + "SAW", + "SCISSOR", + "SCRA", + "SCRIPT", + "SEEN", + "SERVER", + "SHADOW", + "SHELL", + "SHINE", + "SHO", + "SIGHT", + "SIN", + "SMALL", + "SMELT", + "SMOK", + "SNAKE", + "SNO", + "SNOW", + "SOU", + "SO-WAVE", + "SPAR", + "SPEC", + "SPID", + "S-PIN", + "SPRA", + "STAM", + "STARE", + "STEA", + "STONE", + "STORM", + "STRU", + "STRUG", + "STUDEN", + "SUBS", + "SUCID", + "SUN-LIG", + "SUNRIS", + "SUPLY", + "S-WAVE", + "TAILS", + "TANGL", + "TASTE", + "TELLI", + "THANK", + "TONKICK", + "TOOTH", + "TORL", + "TRAIN", + "TRIKICK", + "TUNGE", + "VOLT", + "WA-GUN", + "WATCH", + "WAVE", + "W-BOMB", + "WFALL", + "WFING", + "WHIP", + "WHIRL", + "WIND", + "WOLF", + "WOOD", + "WOR", + "YUJA", +} diff --git a/lib/elfs/names.moon b/lib/elfs/names.moon new file mode 100644 index 0000000..fc7a6b8 --- /dev/null +++ b/lib/elfs/names.moon @@ -0,0 +1,250 @@ +{ + "SEED", + "GRASS", + "FLOWE", + "SHAD", + "CABR", + "SNAKE", + "GOLD", + "COW", + "GUIKI", + "PEDAL", + "DELAN", + "B-FLY", + "BIDE", + "KEYU", + "FORK", + "LAP", + "PIGE", + "PIJIA", + "CAML", + "LAT", + "BIRD", + "BABOO", + "VIV", + "ABOKE", + "PIKAQ", + "RYE", + "SAN", + "BREAD", + "LIDEL", + "LIDE", + "PIP", + "PIKEX", + "ROK", + "JUGEN", + "PUD", + "BUDE", + "ZHIB", + "GELU", + "GRAS", + "FLOW", + "LAFUL", + "ATH", + "BALA", + "CORN", + "MOLUF", + "DESP", + "DAKED", + "MIMI", + "BOLUX", + "KODA", + "GELUD", + "MONK", + "SUMOY", + "GEDI", + "WENDI", + "NILEM", + "NILE", + "NILEC", + "KEZI", + "YONGL", + "HUDE", + "WANLI", + "GELI", + "GUAIL", + "MADAQ", + "WUCI", + "WUCI", + "MUJEF", + "JELLY", + "SICIB", + "GELU", + "NELUO", + "BOLI", + "JIALE", + "YED", + "YEDE", + "CLO", + "SCARE", + "AOCO", + "DEDE", + "DEDEI", + "BAWU", + "JIUG", + "BADEB", + "BADEB", + "HOLE", + "BALUX", + "GES", + "FANT", + "QUAR", + "YIHE", + "SWAB", + "SLIPP", + "CLU", + "DEPOS", + "BILIY", + "YUANO", + "SOME", + "NO", + "YELA", + "EMPT", + "ZECUN", + "XIAHE", + "BOLEL", + "DEJI", + "MACID", + "XIHON", + "XITO", + "LUCK", + "MENJI", + "GELU", + "DECI", + "XIDE", + "DASAJ", + "DONGN", + "RICUL", + "MINXI", + "BALIY", + "ZENDA", + "LUZEL", + "HELE5", + "0FENB", + "KAIL", + "JIAND", + "CARP", + "JINDE", + "LAPU", + "MUDE", + "YIFU", + "LINLI", + "SANDI", + "HUSI", + "JINC", + "OUMU", + "OUMUX", + "CAP", + "KUIZA", + "PUD", + "TIAO", + "FRMAN", + "CLAU", + "SPARK", + "DRAGO", + "BOLIU", + "GUAIL", + "MIYOU", + "MIY", + "QIAOK", + "BEIL", + "MUKEI", + "RIDED", + "MADAM", + "BAGEP", + "CROC", + "ALIGE", + "OUDAL", + "OUD", + "DADA", + "HEHE", + "YEDEA", + "NUXI", + "NUXIN", + "ROUY", + "ALIAD", + "STICK", + "QIANG", + "LAAND", + "PIQI", + "PI", + "PUPI", + "DEKE", + "DEKEJ", + "NADI", + "NADIO", + "MALI", + "PEA", + "ELECT", + "FLOWE", + "MAL", + "MALI", + "HUSHU", + "NILEE", + "YUZI", + "POPOZ", + "DUZI", + "HEBA", + "XIAN", + "SHAN", + "YEYEA", + "WUY", + "LUO", + "KEFE", + "HULA", + "CROW", + "YADEH", + "MOW", + "ANNAN", + "SUONI", + "KYLI", + "HULU", + "HUDEL", + "YEHE", + "GULAE", + "YEHE", + "BLU", + "GELAN", + "BOAT", + "NIP", + "POIT", + "HELAK", + "XINL", + "BEAR", + "LINB", + "MAGEH", + "MAGEJ", + "WULI", + "YIDE", + "RIVE", + "FISH", + "AOGU", + "DELIE", + "MANTE", + "KONMU", + "DELU", + "HELU", + "HUAN", + "HUMA", + "DONGF", + "JINCA", + "HEDE", + "DEFU", + "LIBY", + "JIAPA", + "MEJI", + "HELE", + "BUHU", + "MILK", + "HABI", + "THUN", + "GARD", + "DON", + "YANGQ", + "SANAQ", + "BANQ", + "LUJ", + "PHIX", + "SIEI", + "EGG", +} diff --git a/main.go b/main.go index cdd4352..c009de3 100644 --- a/main.go +++ b/main.go @@ -1,30 +1,17 @@ package main import ( - "crypto/x509" - "encoding/pem" - "errors" "flag" - "fmt" - "io/ioutil" "log" "math/rand" "net" "net/http" - "net/rpc" - "os" - "path/filepath" - "strconv" "time" - "git.xeserv.us/xena/route/routerpc" - r "github.com/GoRethink/gorethink" - "github.com/Xe/uuid" - "github.com/Yawning/bulb" + "git.xeserv.us/xena/route/server" + "github.com/facebookgo/flagenv" _ "github.com/joho/godotenv/autoload" - "github.com/koding/tunnel" - "github.com/sycamoreone/orc/tor" ) var ( @@ -36,11 +23,7 @@ var ( torHashedPassword = flag.String("tor-hashed-password", "", "Tor hashed password") torPassword = flag.String("tor-password", "hunter2", "Tor clear password") webPort = flag.String("web-port", "9234", "HTTP ingress port for backends and users") -) - -// RPC constants -const ( - RPCPort uint16 = 39453 + domainSuffix = flag.String("domain-suffix", ".apps.xeserv.us", "Domain name suffix associated with the load balancer") ) func main() { @@ -48,7 +31,7 @@ func main() { flagenv.Parse() rand.Seed(time.Now().Unix()) - s, err := create(&ServerConfig{ + s, err := server.New(&server.ServerConfig{ ControlHost: *controlHost, ControlKeyFile: *controlKeyFile, RethinkDBHost: *rethinkDBHost, @@ -56,6 +39,8 @@ func main() { TorDataDir: *torDataDir, TorHashedPassword: *torHashedPassword, TorPassword: *torPassword, + WebPort: *webPort, + DomainSuffix: *domainSuffix, }) if err != nil { log.Fatal(err) @@ -74,182 +59,3 @@ func main() { hs.Serve(l) } - -// Server is the main server type -type Server struct { - cfg *ServerConfig - - sess *r.Session - - torCon *bulb.Conn - torCmd *tor.Cmd - torControlPath string - - rpcS *rpc.Server - rpcAddr string - - ts *tunnel.Server -} - -// ServerConfig configures Server -type ServerConfig struct { - ControlHost, ControlKeyFile string - RethinkDBHost, RethinkDBDatabase string - TorDataDir, TorHashedPassword, TorPassword string -} - -func create(cfg *ServerConfig) (*Server, error) { - session, err := r.Connect(r.ConnectOpts{ - Address: cfg.RethinkDBHost, - Database: cfg.RethinkDBDatabase, - }) - if err != nil { - return nil, err - } - - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, err - } - - torControlPath := filepath.Join(cfg.TorDataDir, fmt.Sprintf("%d.sock", rand.Int63())) - - tc := tor.NewConfig() - tc.Set("DataDirectory", cfg.TorDataDir) - tc.Set("HashedControlPassword", cfg.TorHashedPassword) - tc.Set("SocksPort", "0") - cp := rand.Intn(64512) // 64k - 1k - tc.Set("ControlPort", cp) - - tc.Timeout = 30 * time.Second - log.Println(tc.ToCmdLineFormat()) - - tcmd, err := tor.NewCmd(tc) - if err != nil { - return nil, err - } - - err = tcmd.Start() - if err != nil { - return nil, err - } - - time.Sleep(5 * time.Second) - - bc, err := bulb.Dial("tcp", "127.0.0.1:"+strconv.Itoa(cp)) - if err != nil { - return nil, err - } - - err = bc.Authenticate(cfg.TorPassword) - 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 - } - - var block *pem.Block - - block, _ = pem.Decode([]byte(data)) - pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - - _, err = bc.AddOnion([]bulb.OnionPortSpec{ - bulb.OnionPortSpec{ - VirtPort: RPCPort, - Target: l.Addr().String(), - }, - }, pKey, false) - if err != nil { - return nil, err - } - - rpcs := rpc.NewServer() - - ts, err := tunnel.NewServer(&tunnel.ServerConfig{}) - if err != nil { - return nil, err - } - - s := &Server{ - cfg: cfg, - - sess: session, - - torCon: bc, - torCmd: tcmd, - torControlPath: torControlPath, - - rpcS: rpcs, - rpcAddr: l.Addr().String(), - - ts: ts, - } - - rpcs.RegisterName("Urls", &RPCServer{Server: s}) - go rpcs.Accept(l) - - return s, nil -} - -func (s *Server) onionPath(name string) string { - return filepath.Join(s.cfg.TorDataDir, name) -} - -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == rpc.DefaultRPCPath { - s.rpcS.ServeHTTP(w, r) - return - } - s.ts.ServeHTTP(w, r) -} - -type RPCServer struct { - *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() - - oi, err := rs.torCon.AddOnion([]bulb.OnionPortSpec{ - bulb.OnionPortSpec{ - VirtPort: 80, - Target: "127.0.0.1:" + *webPort, - }, - }, req.PrivKey, false) - if err != nil { - return err - } - - resp.OnionHostname = oi.OnionID + ".onion" - resp.Token = token - - if req.Hostname != "" { - rs.Server.ts.AddHost(req.Hostname, token) - resp.Hostname = req.Hostname - } - rs.Server.ts.AddHost(resp.OnionHostname, token) - - if oi.PrivateKey != nil { - resp.PrivKey = oi.PrivateKey - } else { - resp.PrivKey = req.PrivKey - } - - return nil -} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..a6196b0 --- /dev/null +++ b/server/server.go @@ -0,0 +1,263 @@ +package server + +import ( + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net" + "net/http" + "net/rpc" + "os" + "path/filepath" + "strconv" + "time" + + "git.xeserv.us/xena/route/database" + "git.xeserv.us/xena/route/lib/elfs" + "git.xeserv.us/xena/route/routerpc" + "github.com/Xe/uuid" + "github.com/Yawning/bulb" + "github.com/koding/tunnel" + "github.com/sycamoreone/orc/tor" +) + +// RPC constants +const ( + RPCPort uint16 = 39453 +) + +// Server is the main server type +type Server struct { + cfg *ServerConfig + + db *database.DB + + torCon *bulb.Conn + torCmd *tor.Cmd + torControlPath string + + rpcS *rpc.Server + rpcAddr string + + ts *tunnel.Server +} + +// ServerConfig configures Server +type ServerConfig struct { + ControlHost, ControlKeyFile string + RethinkDBHost, RethinkDBDatabase string + TorDataDir, TorHashedPassword, TorPassword string + WebPort, DomainSuffix string +} + +// New creates a new Server +func New(cfg *ServerConfig) (*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 + } + + torControlPath := filepath.Join(cfg.TorDataDir, fmt.Sprintf("%d.sock", rand.Int63())) + + tc := tor.NewConfig() + tc.Set("DataDirectory", cfg.TorDataDir) + tc.Set("HashedControlPassword", cfg.TorHashedPassword) + tc.Set("SocksPort", "0") + cp := rand.Intn(64512) // 64k - 1k + tc.Set("ControlPort", cp) + + tc.Timeout = 30 * time.Second + log.Println(tc.ToCmdLineFormat()) + + tcmd, err := tor.NewCmd(tc) + if err != nil { + return nil, err + } + + err = tcmd.Start() + if err != nil { + return nil, err + } + + time.Sleep(5 * time.Second) + + bc, err := bulb.Dial("tcp", "127.0.0.1:"+strconv.Itoa(cp)) + if err != nil { + return nil, err + } + + err = bc.Authenticate(cfg.TorPassword) + 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 + } + + var block *pem.Block + + block, _ = pem.Decode([]byte(data)) + pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + _, err = bc.AddOnion([]bulb.OnionPortSpec{ + bulb.OnionPortSpec{ + VirtPort: RPCPort, + Target: l.Addr().String(), + }, + }, pKey, false) + if err != nil { + return nil, err + } + + rpcs := rpc.NewServer() + + ts, err := tunnel.NewServer(&tunnel.ServerConfig{}) + if err != nil { + return nil, err + } + + s := &Server{ + cfg: cfg, + + db: db, + + torCon: bc, + torCmd: tcmd, + torControlPath: torControlPath, + + rpcS: rpcs, + rpcAddr: l.Addr().String(), + + ts: ts, + } + + rpcs.RegisterName("Urls", &RPCServer{Server: s}) + go rpcs.Accept(l) + + 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) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == rpc.DefaultRPCPath { + s.rpcS.ServeHTTP(w, r) + return + } + s.ts.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 + } + + ports := []bulb.OnionPortSpec{ + genPortSpec(80, "127.0.0.1:"+s.cfg.WebPort), + } + + _, err = s.torCon.AddOnion(ports, pKey, false) + if err != nil { + return err + } + + s.ts.AddHost(rt.Hostname, rt.Token) + s.ts.AddHost(rt.OnionHostname, rt.Token) + + 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 +} + +func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error { + if req.APIKey != "hunter2" { + return errors.New("invalid api key") + } + + token := uuid.New() + + oi, err := rs.torCon.AddOnion([]bulb.OnionPortSpec{ + bulb.OnionPortSpec{ + VirtPort: 80, + Target: "127.0.0.1:" + rs.cfg.WebPort, + }, + }, req.PrivKey, false) + if err != nil { + return err + } + + resp.OnionHostname = oi.OnionID + ".onion" + resp.Token = token + + if req.Hostname != "" { + rs.Server.ts.AddHost(req.Hostname, token) + resp.Hostname = req.Hostname + } else { + resp.Hostname = elfs.MakeName() + rs.cfg.DomainSuffix + rs.ts.AddHost(resp.Hostname, token) + } + rs.Server.ts.AddHost(resp.OnionHostname, token) + + if oi.PrivateKey != nil { + resp.PrivKey = oi.PrivateKey + } else { + resp.PrivKey = req.PrivKey + } + + err = rs.db.SaveRoute(resp) + if err != nil { + return err + } + + return nil +}