func handlePortAnnounced( conn net.Conn, connection_data *structs.ConnData, packet_data *structs.PacketData, stringParts []string, ) error { connection_data.ServerAddr = structs.StripPort(conn.RemoteAddr().String()) + ":" + stringParts[1] return nil }
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 }
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) }
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 }
func RPCAuthenticateWithTokenMessage(conn net.Conn, connection_data *structs.ConnData, packet_data *structs.PacketData) error { // Unmarshal the data msg := new(protocol.AuthenticateWithTokenMessage) err := proto.Unmarshal(packet_data.Content, msg) if err != nil { return err } // Split the token token := string(msg.Token) parts := strings.Split(token, ":") // uid is the first part id, err := strconv.Atoi(parts[0]) if err != nil { return err } // Convert it to the Npid npid := structs.IdToNpid(id) // Set connection data connection_data.Id = id connection_data.Npid = npid connection_data.Token = token // Verify! sess_string, err := environment.Env.Redis.Get("session:" + token).Result() if err != nil { return err } // Format is: ip;rank;username sess_parts := strings.Split(sess_string, ";") // Verify that user has the same IP as the one used to log in if sess_parts[0] != structs.StripPort(conn.RemoteAddr().String()) { return WrongIPAddress } // Get the rank id and save it in the session connection_data.RankId, err = strconv.Atoi(sess_parts[1]) if err != nil { return err } // Authenticate the session connection_data.Authenticated = true connection_data.Username = sess_parts[2] // Add connection to the storage storage.SetClientConnection(npid, connection_data) // Return a response err = reply.Reply(conn, packet_data.Header.Id, &protocol.AuthenticateResultMessage{ Result: proto.Int32(0), Npid: &npid, SessionToken: msg.Token, }) if err != nil { return err } // No idea what this thing is supposed to do time.AfterFunc(time.Millisecond*900, func() { err = reply.Reply(conn, 0, &protocol.AuthenticateExternalStatusMessage{ Status: proto.Int32(0), }) if err != nil { logger.Warning(err) } }) return nil }