Ejemplo n.º 1
0
// Must be called in a transaction, holding session and waiting locks.
func handleAuthComplete(conn *userConn, idemChanges []store.Change) {

	// We're no longer waiting for authentication to complete.
	delete(waiting, conn.waitingAuth.requestId)
	waitingAuth := conn.waitingAuth
	conn.waitingAuth = nil

	// Find the created session entity.
	newSessionId := uint64(0)
	for i := range idemChanges {
		if idemChanges[i].Key == "kind" &&
			idemChanges[i].Value == "session" {

			newSessionId = idemChanges[i].TargetEntity
			break
		}
	}

	// Check session exists.
	session := store.GetEntity(newSessionId)
	if session == nil ||
		session.Value("kind") != "session" {
		sendAuthFail(conn, "Session Deleted")
		return
	}

	// Check it is attached to a user.
	attachedTo := store.AllAttachedTo(newSessionId)
	if len(attachedTo) == 0 {
		sendAuthFail(conn, "User Deleted")
		return
	}

	// Check that user has a username.
	user := store.GetEntity(attachedTo[0])
	if user.Value("name username") == "" {
		sendAuthFail(conn, "Username Already In Use")

		// TODO: Should request deletion of user, leaning on timeout.
		return
	}

	// This connection is successfully authenticated with that session.
	conn.session = newSessionId

	// If there is an existing connection associated with that session,
	// drop it.
	if existingConn := sessions[newSessionId]; existingConn != nil {
		existingConn.conn.Close()
	}

	sessions[conn.session] = conn
	sendAuthSuccess(conn, waitingAuth.password)
}
Ejemplo n.º 2
0
// Can only be called from the handling goroutine for conn,
// inside a transaction.
func followUser(conn *userConn, followId uint64) {
	conn.following = append(conn.following, followId)

	user := store.GetEntity(followId)

	// Send the user's username.
	var dataMsg cliproto_down.UserData
	dataMsg.UserId = new(uint64)
	dataMsg.Key = new(string)
	dataMsg.Value = new(string)
	dataMsg.FirstUnapplied = new(uint64)
	*dataMsg.UserId = followId
	*dataMsg.Key = "name username"
	*dataMsg.Value = user.Value(*dataMsg.Key)
	*dataMsg.FirstUnapplied = store.InstructionFirstUnapplied()
	conn.conn.SendProto(6, &dataMsg)

	// Send information on all the user's sessions.
	user.Attached(func(key string) {
		*dataMsg.Key = key
		*dataMsg.Value = "true"
		conn.conn.SendProto(6, &dataMsg)
	})

	// Send a done message to indicate that we are finished.
	var doneMsg cliproto_down.UserDataDone
	doneMsg.UserId = dataMsg.UserId
	doneMsg.FirstUnapplied = dataMsg.FirstUnapplied
	conn.conn.SendProto(7, &doneMsg)
}
Ejemplo n.º 3
0
// Must be called while holding no locks and not in a transaction.
func deliver(msg *relay.UserMessage) {

	// If we have a connection to the recipient session,
	// deliver the message.
	sessionsLock.Lock()

	userConn, exists := sessions[msg.Recipient]
	if exists {
		// Try to deliver the message to this connection,
		// without blocking.
		// If its delivery channel is full, just drop it.
		select {
		case userConn.deliver <- msg:
			break
		default:
			break
		}
	}

	sessionsLock.Unlock()

	if exists {
		return
	}

	// Otherwise, decrement TTL. If it's 0, discard the message.
	msg.Ttl--
	if msg.Ttl == 0 {
		return
	}

	// Otherwise, look for another node which is connected to this session.
	store.StartTransaction()

	session := store.GetEntity(msg.Recipient)
	if session == nil || session.Value("kind") != "session" {
		// Unknown session; discard the message.
		store.EndTransaction()
		return
	}

	nodes := session.AllAttached()
	store.EndTransaction()

	if len(nodes) == 0 {
		// Shouldn't happen, sessions are transient,
		// should be deleted before reaching here.
		// Just discard the message.
		return
	}

	// Forward the message to a random one of those nodes.
	r := rand.Intn(len(nodes))
	relay.Forward(uint16(nodes[r]), msg)
}
Ejemplo n.º 4
0
func namelessTimeout(id uint64) {
	store.StartTransaction()
	defer store.EndTransaction()

	// If the timeout has been removed, do nothing.
	if namelessRemoveTimeouts[id] == nil {
		return
	}

	// Remove nameless user.
	// TODO: Should make sure we only do this once.
	user := store.GetEntity(id)
	if user != nil {
		chset := make([]store.Change, 1)
		chset[0].TargetEntity = id
		chset[0].Key = "id"
		chset[0].Value = ""

		req := makeRequest(chset)
		go chrequest.Request(req)
	}
}
Ejemplo n.º 5
0
func orphanTimeout(id uint64) {
	store.StartTransaction()
	defer store.EndTransaction()

	// If the timeout has been removed, do nothing.
	if namelessRemoveTimeouts[id] == nil {
		return
	}

	// Detach ourselves from the session.
	// TODO: Should make sure we only do this once.
	ourAttachStr := "attach " + strconv.FormatUint(uint64(config.Id()), 10)
	session := store.GetEntity(id)
	if session != nil {
		chset := make([]store.Change, 1)
		chset[0].TargetEntity = id
		chset[0].Key = ourAttachStr
		chset[0].Value = ""

		req := makeRequest(chset)
		go chrequest.Request(req)
	}
}
Ejemplo n.º 6
0
// Can only be called from the handling goroutine for conn.
func handleFollowUser(conn *userConn, content []byte) {
	var msg cliproto_up.FollowUser
	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
	}

	// Check we're not already following this user.
	// If we are, discard the message.
	for _, existing := range conn.following {
		if existing == *msg.UserId {
			return
		}
	}

	// Check this ID is actually a user entity.
	otherUser := store.GetEntity(*msg.UserId)
	if otherUser == nil || otherUser.Value("kind") != "user" {
		sendFollowUserIdFail(conn, *msg.UserId, "No Such User")
		return
	}

	// Start following this user.
	followUser(conn, *msg.UserId)
}
Ejemplo n.º 7
0
func handleApplied(slot uint64, idemChanges []store.Change) {

	relativeSlot := int(slot - store.InstructionStart())
	slots := store.InstructionSlots()
	requestNode := slots[relativeSlot][0].ChangeRequest().RequestNode
	requestId := slots[relativeSlot][0].ChangeRequest().RequestId

	sessionsLock.Lock()
	defer sessionsLock.Unlock()

	waitingLock.Lock()
	defer waitingLock.Unlock()

	// If there is a connection which has this as its waiting request,
	// have it complete authentication.
	if requestNode == config.Id() {
		waitingConn := waiting[requestId]
		if waitingConn != nil {
			handleAuthComplete(waitingConn, idemChanges)
		}
	}

	ourAttachStr := "attach " + strconv.FormatUint(uint64(config.Id()), 10)
	for i := range idemChanges {

		key := idemChanges[i].Key
		value := idemChanges[i].Value

		if strings.HasPrefix(key, "attach ") {

			// If we were just detached from a session,
			// drop any connections associated with it,
			// and if it was in our attach timeouts, remove it.
			if key == ourAttachStr && value == "" {

				sessionId := idemChanges[i].TargetEntity
				if conn := sessions[sessionId]; conn != nil {
					log.Print("client/listener: " +
						"detached from existing " +
						"session")

					conn.conn.Close()
				}

				if orphanAttachTimeouts[sessionId] != nil {
					orphanAttachTimeouts[sessionId].Stop()
					delete(orphanAttachTimeouts, sessionId)
				}
			}

			// If we were just attached to a session,
			// but have no connection associated with that session,
			// add an attach timeout.
			// and if it was in our attach timeouts, remove it.
			if key == ourAttachStr && value != "" {

				sessionId := idemChanges[i].TargetEntity
				if sessions[sessionId] == nil {
					startOrphanTimeout(sessionId)
				}
			}

			// Handle a session attaching/detaching from a user.
			entityId := idemChanges[i].TargetEntity
			entity := store.GetEntity(entityId)
			if entity != nil && entity.Value("kind") == "user" {

				// Tell following connections when a session
				// attaches or detaches from a followed user.
				// TODO: Expensive loop,
				// should maintain index to avoid this.
				for conn, _ := range connections {
					following := false
					for _, fw := range conn.following {
						if fw == entityId {
							following = true
							break
						}
					}

					if !following {
						continue
					}

					sendUserChange(conn, entityId,
						key, value)
				}
			}
		}
	}

	// If we've created a nameless user, start a timer to delete it.
	for i := range idemChanges {
		if idemChanges[i].Key != "kind" ||
			idemChanges[i].Value != "user" {

			continue
		}

		newUserId := idemChanges[i].TargetEntity
		newUser := store.GetEntity(newUserId)
		if newUser != nil && newUser.Value("name username") == "" {
			startNamelessTimeout(newUserId)
		}
	}
}
Ejemplo n.º 8
0
func handleDeleting(entityId uint64) {

	entity := store.GetEntity(entityId)
	kind := entity.Value("kind")

	switch kind {

	// If it's a user deletion, find any sessions attached to the user,
	// and drop any connections attached to those.
	// Cancel any follows of that user.
	// Additionally, remove any corresponding nameless user delete timeout.
	case "user":
		connectionsLock.Lock()
		defer connectionsLock.Unlock()

		// TODO: Expensive loop, should maintain index to avoid this.
		for conn, _ := range connections {
			for i, followed := range conn.following {
				if followed != entityId {
					continue
				}

				fwing := conn.following
				fwing = append(fwing[:i], fwing[i+1:]...)
				conn.following = fwing

				sendStoppedFollowing(conn, entityId,
					"User Deleted")
			}
		}

		sessionIds := entity.AllAttached()
		if len(sessionIds) > 0 {
			sessionsLock.Lock()
			defer sessionsLock.Unlock()

			for _, sessionId := range sessionIds {
				if conn := sessions[sessionId]; conn != nil {
					log.Print("client/listener: " +
						"connected user deleted")

					conn.conn.Close()
				}
			}
		}

		if namelessRemoveTimeouts[entityId] != nil {
			namelessRemoveTimeouts[entityId].Stop()
			delete(namelessRemoveTimeouts, entityId)
		}

	// If it's a session, and we have a connection associated to it,
	// close that connection and remove it from our map.
	case "session":
		sessionsLock.Lock()
		defer sessionsLock.Unlock()

		if conn := sessions[entityId]; conn != nil {
			log.Print("client/listener: connected session deleted")
			conn.conn.Close()
			delete(sessions, entityId)
		}

		// If we had a timeout waiting to detach ourselves from this
		// session, cancel it.
		if orphanAttachTimeouts[entityId] != nil {
			orphanAttachTimeouts[entityId].Stop()
			delete(orphanAttachTimeouts, entityId)
		}
	}
}
Ejemplo n.º 9
0
// 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
	}
}