// Can only be called from the handling goroutine for conn. func handleFollowUsername(conn *userConn, content []byte) { var msg cliproto_up.FollowUsername if err := proto.Unmarshal(content, &msg); err != nil { conn.conn.Close() return } // Start transaction. store.StartTransaction() defer store.EndTransaction() sessionsLock.Lock() defer sessionsLock.Unlock() // Authentication check. if conn.session == 0 { conn.conn.Close() return } // Lookup this user. followId := store.NameLookup("user", "name username", *msg.Username) if followId == 0 { sendFollowUsernameFail(conn, *msg.Username, "No Such User") return } // Check we're not already following this user. // If we are, discard the message. for _, existing := range conn.following { if existing == followId { return } } // Start following this user. followUser(conn, followId) }
// Can only be called from the handling goroutine for conn. func handleAuth(conn *userConn, content []byte) { var msg cliproto_up.Authenticate if err := proto.Unmarshal(content, &msg); err != nil { conn.conn.Close() return } // Try to get information, then release locks and hash the password. // If the situation changes we may have to hash it again anyway, // but scrypt hashing is extremely expensive and we want to try to // do this without holding our locks in the vast majority of cases. var tryPassGenerated bool var trySaltGenerated bool var tryPass, trySalt, tryKey string if *msg.Password != "" { tryPass = *msg.Password store.StartTransaction() userId := store.NameLookup("user", "name username", *msg.Username) if userId != 0 { user := store.GetEntity(userId) if user != nil { trySalt = user.Value("auth salt") } } store.EndTransaction() if trySalt == "" { trySaltGenerated = true var err error trySalt, tryKey, err = genRandomSalt(conn, []byte(tryPass)) if err != nil { return } } else { var err error tryPass, err = genKey(conn, []byte(tryPass), []byte(trySalt)) if err != nil { return } } } else { tryPassGenerated = true trySaltGenerated = true var err error tryPass, trySalt, tryKey, err = genRandomPass(conn) if err != nil { return } } // TODO: Validate username and password against constraints. // We hold this through quite a lot of logic. // Would be good to be locked less. store.StartTransaction() defer store.EndTransaction() sessionsLock.Lock() defer sessionsLock.Unlock() waitingLock.Lock() defer waitingLock.Unlock() if conn.session != 0 || conn.waitingAuth != nil { conn.conn.Close() return } userId := store.NameLookup("user", "name username", *msg.Username) if userId != 0 { if *msg.Password == "" { sendAuthFail(conn, "Invalid Password") return } // The user already exists. user := store.GetEntity(userId) // Try to authenticate them to it. var key string // If their salt and password matches our attempt above, // we can just take that key. salt := user.Value("auth salt") if trySalt == salt && tryPass == *msg.Password { key = tryKey } else { saltBytes := []byte(user.Value("auth salt")) passBytes := []byte(*msg.Password) var err error key, err = genKey(conn, passBytes, saltBytes) if err != nil { return } } if user.Value("auth password") != string(key) { sendAuthFail(conn, "Invalid Password") return } // It's the real user. if *msg.SessionId != 0 { // They are attaching to an existing session. // Check it exists and is attached to this user. strSessionId := strconv.FormatUint(*msg.SessionId, 10) if user.Value("attach "+strSessionId) == "" { sendAuthFail(conn, "Invalid Session") return } // The session does exist. conn.session = *msg.SessionId // If this node is already attached to // the session, drop the other connection. if sessions[conn.session] != nil { sessions[conn.session].conn.Close() } else { // Create change attaching this node ID // to the session. id := config.Id() idStr := strconv.FormatUint(uint64(id), 10) chset := make([]store.Change, 1) chset[0].TargetEntity = conn.session chset[0].Key = "attach " + idStr chset[0].Value = "true" req := makeRequest(chset) go chrequest.Request(req) } // Put us in the sessions map. sessions[conn.session] = conn // Tell the client they authenticated successfully. sendAuthSuccess(conn, "") } else { // They are creating a new session. req := makeNewSessionRequest(userId) go chrequest.Request(req) // Stuff details in waiting auth. conn.waitingAuth = new(authData) conn.waitingAuth.msg = msg conn.waitingAuth.requestId = req.RequestId waiting[conn.waitingAuth.requestId] = conn } } else { // The user does not already exist. // Check they weren't trying to attach to a session. if *msg.SessionId != 0 { sendAuthFail(conn, "User Does Not Exist") return } // We're creating a new user. newUser := *msg.Username newPass := *msg.Password if !strings.HasPrefix(newUser, "Guest-") { // We're creating a new non-guest user. // Make sure they have a password. if newPass == "" { sendAuthFail(conn, "No Password") return } var salt string var hash string if tryPass == newPass && trySaltGenerated { salt = trySalt hash = tryKey } else { passBytes := []byte(newPass) var err error salt, hash, err = genRandomSalt(conn, passBytes) if err != nil { return } } // Create the new user. req := makeNewUserRequest(newUser, hash, salt, false) go chrequest.Request(req) // Stuff details in waiting auth. conn.waitingAuth = new(authData) conn.waitingAuth.msg = msg conn.waitingAuth.requestId = req.RequestId waiting[conn.waitingAuth.requestId] = conn return } // We're creating a new guest user. // Guests get automatic passwords, and can't set them. if newPass != "" { sendAuthFail(conn, "Cannot Set Password For Guest User") return } var hash string var salt string if tryPassGenerated && trySaltGenerated { newPass = tryPass salt = trySalt hash = tryKey } else { var err error newPass, salt, hash, err = genRandomPass(conn) if err != nil { return } } waitingLock.Lock() // Create the new user. req := makeNewUserRequest(newUser, hash, salt, true) go chrequest.Request(req) // Stuff details in waiting auth. conn.waitingAuth = new(authData) conn.waitingAuth.msg = msg conn.waitingAuth.requestId = req.RequestId waiting[conn.waitingAuth.requestId] = conn waitingLock.Unlock() return } }