diff --git a/cmd/ck-client/admin.go b/cmd/ck-client/admin.go index 29dc773..9b00a52 100644 --- a/cmd/ck-client/admin.go +++ b/cmd/ck-client/admin.go @@ -75,7 +75,7 @@ func adminHandshake(sta *client.State) (*administrator, error) { return a, nil } -func (a *administrator) getCommand() []byte { +func (a *administrator) getRequest() (req []byte, err error) { fmt.Println("Select your command") fmt.Println(`1 listActiveUsers none []uids 2 listAllUsers none []userinfo @@ -85,15 +85,18 @@ func (a *administrator) getCommand() []byte { fmt.Scanln(&cmd) switch cmd { case "1": - return a.request([]byte{0x01}) + req = a.request([]byte{0x01}) + return case "2": - return a.request([]byte{0x02}) + req = a.request([]byte{0x02}) + return case "3": fmt.Println("Enter UID") var b64UID string fmt.Scanln(&b64UID) UID, _ := base64.StdEncoding.DecodeString(b64UID) - return a.request(append([]byte{0x03}, UID...)) + req = a.request(append([]byte{0x03}, UID...)) + return case "4": var uinfo UserInfo var b64UID string @@ -114,9 +117,10 @@ func (a *administrator) getCommand() []byte { fmt.Printf("ExpiryTime:") fmt.Scanf("%d", &uinfo.ExpiryTime) marshed, _ := json.Marshal(uinfo) - return a.request(append([]byte{0x04}, marshed...)) + req = a.request(append([]byte{0x04}, marshed...)) + return default: - return nil + return nil, errors.New("Unreconised cmd") } } diff --git a/cmd/ck-client/ck-client.go b/cmd/ck-client/ck-client.go index 2593c29..7a536eb 100644 --- a/cmd/ck-client/ck-client.go +++ b/cmd/ck-client/ck-client.go @@ -86,9 +86,14 @@ func adminPrompt(sta *client.State) error { if err != nil { return err } + log.Println(err) buf := make([]byte, 16000) for { - req := a.getCommand() + req, err := a.getRequest() + if err != nil { + log.Println(err) + continue + } a.adminConn.Write(req) n, err := a.adminConn.Read(buf) if err != nil { @@ -112,7 +117,7 @@ func main() { // The proxy port,should be 443 var remotePort string var pluginOpts string - var isAdmin bool + var isAdmin *bool log.SetFlags(log.LstdFlags | log.Lshortfile) @@ -131,7 +136,7 @@ func main() { flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443") flag.StringVar(&pluginOpts, "c", "ckclient.json", "pluginOpts: path to ckclient.json or options seperated with semicolons") askVersion := flag.Bool("v", false, "Print the version number") - isAdmin = *flag.Bool("a", false, "Admin mode") + isAdmin = flag.Bool("a", false, "Admin mode") printUsage := flag.Bool("h", false, "Print this message") flag.Parse() @@ -151,7 +156,12 @@ func main() { // sessionID is usergenerated. There shouldn't be a security concern because the scope of // sessionID is limited to its UID. rand.Seed(time.Now().UnixNano()) - sessionID := rand.Uint32() + var sessionID uint32 + if *isAdmin { + sessionID = 0 + } else { + sessionID = rand.Uint32() + } // opaque is used to generate the padding of session ticket sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now, sessionID) @@ -160,7 +170,7 @@ func main() { log.Fatal(err) } - if isAdmin { + if *isAdmin { err = adminPrompt(sta) if err != nil { log.Println(err) diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index 424d6b6..b3c65fc 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -16,6 +16,7 @@ import ( mux "github.com/cbeuw/Cloak/internal/multiplex" "github.com/cbeuw/Cloak/internal/server" + "github.com/cbeuw/Cloak/internal/server/usermanager" "github.com/cbeuw/Cloak/internal/util" ) @@ -78,13 +79,12 @@ func dispatchConnection(conn net.Conn, sta *server.State) { return } - if bytes.Equal(UID, sta.AdminUID) { + finishHandshake := func() error { reply := server.ComposeReply(ch) _, err = conn.Write(reply) if err != nil { - log.Printf("Sending reply to remote: %v\n", err) go conn.Close() - return + return err } // Two discarded messages: ChangeCipherSpec and Finished @@ -92,12 +92,19 @@ func dispatchConnection(conn net.Conn, sta *server.State) { for c := 0; c < 2; c++ { _, err = util.ReadTLS(conn, discardBuf) if err != nil { - log.Printf("Reading discarded message %v: %v\n", c, err) go conn.Close() - return + return err } } + return nil + } + if bytes.Equal(UID, sta.AdminUID) && sessionID == 0 { + err = finishHandshake() + if err != nil { + log.Println(err) + return + } c := sta.Userpanel.MakeController(sta.AdminUID) for { n, err := conn.Read(data) @@ -107,6 +114,7 @@ func dispatchConnection(conn net.Conn, sta *server.State) { } resp, err := c.HandleRequest(data[:n]) if err != nil { + log.Println(err) return } _, err = conn.Write(resp) @@ -117,33 +125,26 @@ func dispatchConnection(conn net.Conn, sta *server.State) { } } - user, err := sta.Userpanel.GetAndActivateUser(UID) + + var user *usermanager.User + if bytes.Equal(UID, sta.AdminUID) { + user, err = sta.Userpanel.GetAndActivateAdminUser(UID) + } else { + user, err = sta.Userpanel.GetAndActivateUser(UID) + } if err != nil { log.Printf("+1 unauthorised user from %v, uid: %x\n", conn.RemoteAddr(), UID) goWeb(data) return } - reply := server.ComposeReply(ch) - _, err = conn.Write(reply) + err = finishHandshake() if err != nil { - log.Printf("Sending reply to remote: %v\n", err) - go conn.Close() + log.Println(err) return } - // Two discarded messages: ChangeCipherSpec and Finished - discardBuf := make([]byte, 1024) - for c := 0; c < 2; c++ { - _, err = util.ReadTLS(conn, discardBuf) - if err != nil { - log.Printf("Reading discarded message %v: %v\n", c, err) - go conn.Close() - return - } - } - - if sesh, existing := user.GetOrCreateSession(sessionID, mux.MakeObfs(UID), mux.MakeDeobfs(UID), util.ReadTLS); existing { + if sesh, existing := user.GetSession(sessionID, mux.MakeObfs(UID), mux.MakeDeobfs(UID), util.ReadTLS); existing { sesh.AddConnection(conn) return } else { diff --git a/internal/multiplex/obfs.go b/internal/multiplex/obfs.go index 59f528e..505e255 100644 --- a/internal/multiplex/obfs.go +++ b/internal/multiplex/obfs.go @@ -12,7 +12,6 @@ type Deobfser func([]byte) (*Frame, error) // For each frame, the three parts of the header is xored with three keys. // The keys are generated from the SID and the payload of the frame. -// FIXME: this code will panic if len(data)<18. func genXorKeys(secret []byte, data []byte) (i uint32, ii uint32, iii uint32) { h := xxhash.New32() ret := make([]uint32, 3) diff --git a/internal/multiplex/session.go b/internal/multiplex/session.go index e7566c2..40e23c3 100644 --- a/internal/multiplex/session.go +++ b/internal/multiplex/session.go @@ -17,7 +17,7 @@ var ErrBrokenSession = errors.New("broken session") var errRepeatSessionClosing = errors.New("trying to close a closed session") type Session struct { - id uint32 // This field isn't acutally used + id uint32 // Used in Stream.Write. Add multiplexing headers, encrypt and add TLS header obfs Obfser diff --git a/internal/server/state.go b/internal/server/state.go index b0d1167..8132f5b 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -16,6 +16,7 @@ type rawConfig struct { WebServerAddr string PrivateKey string AdminUID string + SingleUser bool } type stateManager interface { ParseConfig(string) error diff --git a/internal/server/usermanager/user.go b/internal/server/usermanager/user.go index e512bdd..5c3dcd2 100644 --- a/internal/server/usermanager/user.go +++ b/internal/server/usermanager/user.go @@ -65,13 +65,6 @@ func (u *User) updateInfo(uinfo UserInfo) { u.setExpiryTime(uinfo.ExpiryTime) } -func (u *User) GetSession(sessionID uint32) *mux.Session { - u.sessionsM.RLock() - sesh := u.sessions[sessionID] - u.sessionsM.RUnlock() - return sesh -} - func (u *User) PutSession(sessionID uint32, sesh *mux.Session) { u.sessionsM.Lock() u.sessions[sessionID] = sesh @@ -89,7 +82,7 @@ func (u *User) DelSession(sessionID uint32) { u.sessionsM.Unlock() } -func (u *User) GetOrCreateSession(sessionID uint32, obfs mux.Obfser, deobfs mux.Deobfser, obfsedRead func(net.Conn, []byte) (int, error)) (sesh *mux.Session, existing bool) { +func (u *User) GetSession(sessionID uint32, obfs mux.Obfser, deobfs mux.Deobfser, obfsedRead func(net.Conn, []byte) (int, error)) (sesh *mux.Session, existing bool) { // TODO: session cap u.sessionsM.Lock() if sesh = u.sessions[sessionID]; sesh != nil { diff --git a/internal/server/usermanager/userpanel.go b/internal/server/usermanager/userpanel.go index ce7e976..d1b774a 100644 --- a/internal/server/usermanager/userpanel.go +++ b/internal/server/usermanager/userpanel.go @@ -92,6 +92,31 @@ func (up *Userpanel) backupDB(bakPath string) error { var ErrUserNotFound = errors.New("User does not exist in db") var ErrUserNotActive = errors.New("User is not active") +func (up *Userpanel) GetAndActivateAdminUser(AdminUID []byte) (*User, error) { + up.activeUsersM.Lock() + var arrUID [32]byte + copy(arrUID[:], AdminUID) + if user, ok := up.activeUsers[arrUID]; ok { + up.activeUsersM.Unlock() + return user, nil + } + + uinfo := UserInfo{ + UID: AdminUID, + SessionsCap: 1e9, + UpRate: 1e12, + DownRate: 1e12, + UpCredit: 1e15, + DownCredit: 1e15, + ExpiryTime: 1e15, + } + + user := MakeUser(up, &uinfo) + up.activeUsers[arrUID] = user + up.activeUsersM.Unlock() + return user, nil +} + // TODO: expiry check // GetUser is used to retrieve a user if s/he is active, or to retrieve the user's infor @@ -253,7 +278,6 @@ func (up *Userpanel) addNewUser(uinfo UserInfo) error { if err != nil { return err } - // FIXME: obnoxious code if err = b.Put([]byte("SessionsCap"), u32ToB(uinfo.SessionsCap)); err != nil { return err }