func RPCStorageSendRandomStringMessage(conn net.Conn, connection_data *structs.ConnData, packet_data *structs.PacketData) error {
	// Parse the message
	msg := new(protocol.StorageSendRandomStringMessage)
	err := proto.Unmarshal(packet_data.Content, msg)
	if err != nil {
		return err
	}

	// Get the string and split it by spaces
	random_string := msg.GetRandomString()
	parts := strings.Fields(random_string)

	if len(parts) != 2 {
		return nil
	}

	// First part of the string is a header
	switch parts[0] {
	case "troll":
		// Deprecated
		logger.Debugf("Handling aCI2 request from %X", connection_data.Npid)
		return aci.HandleCI2(conn, connection_data, packet_data, parts)
	case "roll":
		// Will be used soon
		logger.Debugf("Handling aCI3 request from %X", connection_data.Npid)
		return aci.HandleCI3(conn, connection_data, packet_data, parts)
	case "fal":
		// Currently used
		logger.Debugf("Handling aCI2.5 request from %X", connection_data.Npid)
		return aci.HandleCI25(conn, connection_data, packet_data, parts)
	case "dis":
		// Should call the events system
		logger.Debugf("Handling disconnect message from %X", connection_data.Npid)
		return handleClientLeft(conn, connection_data, packet_data, parts)
	case "port":
		// Not sure if it is used
		logger.Debugf("Handling port announce from %X", connection_data.Npid)
		return handlePortAnnounced(conn, connection_data, packet_data, parts)
	}

	return nil
}
Exemplo n.º 2
0
func Reply(conn net.Conn, id uint32, msg proto.Message) error {
	// Reflect the passed struct
	val := reflect.ValueOf(msg)

	// Make sure it's a pointer
	if val.Kind() != reflect.Ptr {
		return InvalidMessage
	}

	// Get original struct's name
	name := val.Elem().Type().Name()

	// Map struct's name to the ID
	typeID, found := name_to_id[name]
	if !found {
		return NoMappingFound
	}

	// Marshal the data
	data, err := proto.Marshal(msg)
	if err != nil {
		return err
	}

	// Log that we are sending a message
	logger.Debugf("Sent message %d (ID: %d) to %s", typeID, id, structs.StripPort(conn.RemoteAddr().String()))

	// Generate a new data buffer
	buffer := new(bytes.Buffer)

	// Write the signature
	err = binary.Write(buffer, binary.LittleEndian, uint32(signature))
	if err != nil {
		return err
	}

	// The length of the data
	err = binary.Write(buffer, binary.LittleEndian, uint32(len(data)))
	if err != nil {
		return err
	}

	// Message's type
	err = binary.Write(buffer, binary.LittleEndian, uint32(typeID))
	if err != nil {
		return err
	}

	// Message's id (used for responses)
	err = binary.Write(buffer, binary.LittleEndian, id)
	if err != nil {
		return err
	}

	// And write the data
	_, err = buffer.Write(data)
	if err != nil {
		return err
	}

	// Pass it to the connection
	// Send a single, huge TCP packet
	_, err = buffer.WriteTo(conn)
	if err != nil {
		return err
	}

	// We're fine.
	return nil
}
Exemplo n.º 3
0
func (a *HTTPServer) Start() {
	// Create a new token
	m := martini.Classic()

	// GET / - version info
	m.Get("/", func() string {
		// config.VERSION is a const containing the version string
		return "aiw3/np-server " + config.VERSION
	})

	// Remote kick function. Secure before using.
	/*m.Get("/rkick/:name", func(params martini.Params) string {
			var rows []*struct {
				Id int
			}

			err := environment.Env.Database.Query(`
	SELECT
		id
	FROM
		misago_user
	WHERE
		username = ?`, params["name"]).Rows(&rows)

			if err != nil {
				return err.Error()
			}

			if len(rows) != 1 {
				return "User not found"
			}

			npid := structs.IdToNpid(rows[0].Id)

			connection := storage.GetClientConnection(npid)

			if connection == nil {
				return "Not connected"
			}

			connection.IsUnclean = true

			if connection.ServerId != 0 {
				err = utils.KickUser(connection.ServerId, npid, 10001)
				if err != nil {
					return "Cannot kick user"
				}

				return "User marked as unclean and kicked"
			} else {
				// ehmehgehrd spam with packets
				for _, server := range storage.Servers {
					if server != nil && server.Valid {
						utils.KickUser(server.Npid, npid, 10001)
					}
				}
			}

			return "User not connected to a server; marked as unclean"
		})*/

	// GET /authenticate - remauth replacement
	m.Post("/authenticate", func(r *http.Request) string {
		// Read all data from the body into a variable
		// It might be insecure and cause high memory usage if anyone exploits it
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			logger.Error(err)
			return GenerateResponse(false, "Error while reading from POST body", 0, "Anonymous", "*****@*****.**", "0")
		}

		// As specified in the client, "username" (email in our usecase) and password are seperated by &&
		parts := strings.Split(string(body), "&&")

		// There MUST be two parts or the program will panic!
		if len(parts) != 2 {
			return GenerateResponse(false, "Invalid request", 0, "Anonymous", "*****@*****.**", "0")
		}

		// Trim newlines from the email and password
		// In original implementation \0 was also removed, but it seems that golang won't compile a const with \0
		email := strings.Trim(parts[0], trim_cutset)
		password := strings.Trim(parts[1], trim_cutset)

		// Not sure if it's performant, but that's just like the example in Jet documentation
		// This struct is used for query result mapping
		var rows []*struct {
			Id       int
			Username string
			Password string
			Rank     int
			Email    string
			Reason   string
			Expires  time.Time
		}

		// The larger query seems to be faster than two seperate ones
		// Might be because Jet's mapper uses reflection which isn't that fast
		err = environment.Env.Database.Query(`
SELECT
	u.id AS id,
	u.username AS username,
	u.password AS password,
	u.rank_id AS rank,
	u.email AS email,
	b.reason_user AS reason,
	b.expires AS expires
FROM 
	misago_user AS u
LEFT OUTER JOIN
	misago_ban AS b
ON
	(b.test = 0 AND b.ban = u.username OR b.ban = u.email)
OR
	(b.test = 1 AND b.ban = u.username)
OR
	b.ban = u.email	
WHERE
	u.email = ?`, email).Rows(&rows)

		// Probably MySQL went down
		if err != nil {
			logger.Error(err)
			return GenerateResponse(false, "Database error", 0, "Anonymous", "*****@*****.**", "0")
		}

		// There's no email like that!
		if len(rows) != 1 {
			return GenerateResponse(false, "Wrong email or password", 0, "Anonymous", "*****@*****.**", "0")
		}

		// Put it here, because we already know if the user is banned.
		// TODO: Escape #s, either here or in the GenerateResponse function
		if rows[0].Reason != "" && rows[0].Expires.After(time.Now()) {
			return GenerateResponse(
				false,
				"Your account is banned. Reason: "+strings.Trim(rows[0].Reason, "\n\r#"),
				0,
				"Anonymous",
				"*****@*****.**",
				"0",
			)
		}

		// I'm too lazy to add the original Django hash implementation. Some people will have to login onto forums,
		// so Django's auth module can rehash their passwords.
		if rows[0].Password[:7] != "bcrypt$" {
			return GenerateResponse(
				false,
				"Your account is not compatible with the client. Please log in onto forums.",
				0,
				"Anonymous",
				"*****@*****.**",
				"0",
			)
		}

		// I wonder if the bcrypt password checker is timing-attack proof
		err = bcrypt.CompareHashAndPassword([]byte(rows[0].Password[7:]), []byte(password))
		if err != nil {
			return GenerateResponse(false, "Wrong email or password", 0, "Anonymous", "*****@*****.**", "0")
		}

		// Unidecode the username
		username := unidecode.Unidecode(rows[0].Username)

		token := uniuri.NewLen(20)
		existing, err := environment.Env.Redis.Keys("session:" + strconv.Itoa(rows[0].Id) + ":*").Result()
		if err != nil {
			logger.Warning(err)
			return GenerateResponse(false, "Error while accessing session service", 0, "Anonymous", "*****@*****.**", "0")
		}

		if len(existing) > 0 {
			removed, err := environment.Env.Redis.Del(existing...).Result()
			if err != nil {
				logger.Warning(err)
				return GenerateResponse(false, "Error while accessing session service", 0, "Anonymous", "*****@*****.**", "0")
			}

			logger.Debugf("Removed %d old sessions while authenticating %s", removed, username)
		}

		status, err := environment.Env.Redis.Set("session:"+strconv.Itoa(rows[0].Id)+":"+token, structs.StripPort(r.RemoteAddr)+";"+strconv.Itoa(rows[0].Rank)+";"+username).Result()
		if err != nil {
			logger.Warning(err)
			return GenerateResponse(false, "Error while accessing session service", 0, "Anonymous", "*****@*****.**", "0")
		}

		if status != "OK" {
			logger.Warning("Authentication of %s failed. Redis returned %s on set.", username, status)
			return GenerateResponse(false, "Error while accessing session service", 0, "Anonymous", "*****@*****.**", "0")
		}

		// Return a "Success response"
		return GenerateResponse(
			true,
			"Success",
			rows[0].Id,
			strings.Trim(username, "\n\r#"),
			rows[0].Email,
			strconv.Itoa(rows[0].Id)+":"+token,
		)
	})

	// Standard HTTP serving method
	logger.Infof("Serving auth server on %s", environment.Env.Config.HTTP.BindingAddress)
	http.ListenAndServe(environment.Env.Config.HTTP.BindingAddress, m)
}
Exemplo n.º 4
0
func (n *NPServer) HandleConnection(conn net.Conn) {
	// Close the connection after it ends its execution
	defer conn.Close()

	// This is the place where you should put stuff to be executed for the "connection" event
	// The below code is the most non-thread-safe way to create such identifiers
	// If we get enough load, servers can f**k up
	TotalConnections++
	LastCid++
	cid := LastCid

	// Here comes a struct for connection data, that we pass over to handlers
	connection_data := new(structs.ConnData)
	connection_data.Authenticated = false                  // Because we don't want session stealing
	connection_data.ConnectionId = cid                     // Connection ID is mainly used in servers
	connection_data.PresenceData = make(map[string]string) // If we don't init it here, it'll panic later
	connection_data.Connection = conn
	connection_data.Valid = true

	for {
		// Set timeout
		conn.SetReadDeadline(time.Now().Add(60 * time.Second))

		// First 16 bytes are defined in the structs subpackage
		// It consists of four, 4-byte Little Endian unsigned 32bit integers
		// in the following order: signature, length, type, id
		headerBytes := make([]byte, 16)
		_, err := conn.Read(headerBytes)
		if err != nil {
			if err != io.EOF {
				logger.Warningf("Error while reading the header; %s", err)
			}

			break
		}

		// Initialize a bytes mapper
		buf := bytes.NewReader(headerBytes)

		// Map data to the struct
		var packet_header structs.PacketHeader
		err = binary.Read(buf, binary.LittleEndian, &packet_header)
		if err != nil {
			logger.Warningf("Error while mapping packet_header to struct; %s", err)
			continue
		}

		// Signature check
		if packet_header.Signature != packet_signature {
			logger.Warningf(
				"Signature doesn't match (from %s), received 0x%X",
				conn.RemoteAddr().String(),
				packet_header.Signature,
			)

			continue
		}

		// Length from the packet_header specifies how many of the next bytes are the content
		// Pass it over to the handlers which will decode it using protobuf
		contentBytes := make([]byte, packet_header.Length)

		// Set timeout
		conn.SetReadDeadline(time.Now().Add(60 * time.Second))

		// Read the body
		_, err = io.ReadFull(conn, contentBytes)
		if err != nil {
			logger.Warningf("Error while reading; %s", err)
			break
		}

		// Log that we are parsing a RPC message
		logger.Debugf("Received message %d (ID: %d) from %s", packet_header.Type, packet_header.Id, structs.StripPort(conn.RemoteAddr().String()))

		// Generate a new PacketData struct
		packet_data := new(structs.PacketData)
		packet_data.Header = packet_header
		packet_data.Content = contentBytes

		// Pass it to the handlers. Not the best idea to do it using params, we should use
		// a struct, so we will easily be able to inject more variables.
		err = HandleMessage(conn, connection_data, packet_data)
		if err != nil {
			logger.Warningf("Error while handling message %d from %s; %s", packet_data.Header.Id, conn.RemoteAddr().String(), err)
		}
	}

	if connection_data.Authenticated {
		if connection_data.IsServer {
			storage.DeleteServerConnection(connection_data.Npid)
		} else {
			storage.DeleteClientConnection(connection_data.Npid)
		}

	}

	// I guess that count is going to be used by the web API
	TotalConnections--
	connection_data.Valid = false
}