Beispiel #1
0
func (a *NPServer) Start() error {
	// You can't use property, err := sth, because golang will qq
	// This thing binds a classic TCP listener to the server
	var err error
	a.listener, err = net.Listen("tcp", environment.Env.Config.NP.BindingAddress)
	if err != nil {
		logger.Fatalf("Error listening; %s", err)
	}

	defer a.listener.Close()

	logger.Infof("Serving NP server on %s", environment.Env.Config.NP.BindingAddress)

	var tempDelay time.Duration

	for {
		conn, e := a.listener.Accept()

		if e != nil {
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}

				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}

				logger.Errorf("http: Accept error: %v; retrying in %v", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}

			return e
		}

		// Seperated it from the listener code to make it clean
		go a.HandleConnection(conn)
	}

	return nil
}
Beispiel #2
0
func HandleCI25(
	conn net.Conn,
	connection_data *structs.ConnData,
	packet_data *structs.PacketData,
	stringParts []string,
) error {

	// Parse the token
	detection_id, guid, err := parseToken25(stringParts)
	if err != nil {
		return err
	}

	logger.Infof("DETECTION FROM %s: %d", connection_data.Username, detection_id)

	// We already know that the user is unclean, don't waste time handling it.
	if connection_data.IsUnclean {
		return nil
	}

	// check the hwid for bans
	if isHWIDBanned(guid) {
		detection_id = 10100
	}

	// If reason equals NOT_DETECTED, then it's a check
	if detection_id == NOT_DETECTED || detection_id == 41009 {
		// Client is checked, allow it to connect
		connection_data.LastCI = time.Now()

		// Append the GUID
		err = AppendHWID(guid, structs.NpidToId(connection_data.Npid), false)
		if err != nil {
			return err
		}
	} else {
		// Set the connection to unclean
		connection_data.IsUnclean = true

		// Append the GUID
		err = AppendHWID(guid, structs.NpidToId(connection_data.Npid), true)
		if err != nil {
			return err
		}

		if environment.Env.Config.NP.AnticheatInstant {
			// Ban that user
			err = utils.BanUser(connection_data.Username, detection_id, time.Hour*24*14)
			if err != nil {
				return err
			}

			// Kick him!
			if connection_data.ServerId != 0 {
				err = utils.KickUser(connection_data.ServerId, connection_data.Npid, detection_id)
			}
		} else {
			// Add a delayed ban
			err = utils.AddDelayedBan(connection_data.Npid, detection_id, guid)
			if err != nil {
				return err
			}
		}
	}

	return nil
}
Beispiel #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)
}
Beispiel #4
0
func main() {
	// Add the Stdout logger
	logger.AddOutput(logger.Stdout{
		MinLevel: logger.ERROR, //logger.DEBUG,
		Colored:  true,
	})

	// Load settings from config.toml in working directory
	settings := config.Load("./config.toml")

	/*logger.AddOutput(&logger.File{
		MinLevel: logger.WARNING,
		Path:     "./server.log",
	})*/

	// Load the aCI3 key
	err := aci.LoadKey(settings.NP.AnticheatKeyPath)
	if err != nil {
		logger.Fatalf("Cannot load aCI3 key; %s", err)
	} else {
		logger.Infof("Loaded aCI3 key")
	}

	// Start the NewRelic client if it's enabled in the config file
	if settings.NewRelic.Enabled {
		agent := gorelic.NewAgent()
		agent.Verbose = settings.NewRelic.Verbose
		agent.NewrelicName = settings.NewRelic.Name
		agent.NewrelicLicense = settings.NewRelic.License
		agent.Run()
	}

	// Generate a Jet database connector.
	// Here, err shows if the connection string syntax is valid.
	// The actual creds are checked during the first query.
	database, err := jet.Open(
		settings.Database.Driver,
		settings.Database.ConnectionString,
	)
	defer database.Close()

	if err != nil {
		logger.Fatalf("Cannot connect to database; %s", err)
	}

	// But Redis connects here! As far as I know, autoreconnect is implemented
	cache := redis.NewTCPClient(&redis.Options{
		Addr:     settings.Redis.Address,
		Password: settings.Redis.Password,
		DB:       int64(settings.Redis.Database),
	})
	defer cache.Close()

	// Set up a new environment object
	environment.SetEnvironment(&environment.Environment{
		Config:   settings,
		Database: database,
		Redis:    cache,
	})

	// This thing is massive
	// The main network platform server that game connects to
	if settings.NP.Enabled {
		np_server := np.New()
		go np_server.Start()
	}

	// HTTP-based remote authentication form and a simple API
	// Is supposed to replace half of 3k
	if settings.HTTP.Enabled {
		http_server := http.New()
		go http_server.Start()
	}

	/*if settings.PlayerLog.Enabled {
		playerlog_server := playerlog.Init(settings.PlayerLog)
		go playerlog_server.Start()
	}

	if settings.Misc.Enabled {
		misc_server := misc.Init(settings.Misc)
		go misc_server.Start()
	}*/

	// select{} stops execution of the program until all goroutines close
	// TODO: add panic recovery!
	if settings.NP.Enabled ||
		settings.PlayerLog.Enabled ||
		settings.Misc.Enabled ||
		settings.HTTP.Enabled {
		select {}
	}
}