// OpenServerDB Opens a database connection to the server database file or if // that file does not exists, creates it and then opens a database connection to it. func OpenServerDB() (*SDB, error) { if err := verifyServerDbPath(); err != nil { // will panic if not verified return nil, logger.LogAppError(err) } conn, err := sql.Open("sqlite3", constants.GetServerDBPath()) if err != nil { return nil, logger.LogAppError(err) } return &SDB{db: conn}, nil }
func retrieve(filter filters.Filter) (*models.APIServerList, error) { var mq MasterQuery var err error if config.Config.SteamConfig.UseWebServerList { mq, err = NewMasterWebQuery(filter) } else { mq, err = NewMasterQuery(filter) } if err != nil { return nil, logger.LogSteamErrorf("Master server error: %s", err) } if filter.Game.IgnoreInfo && filter.Game.IgnorePlayers && filter.Game.IgnoreRules { return nil, logger.LogAppErrorf("Cannot ignore all three AS2 requests!") } data := a2sData{} hg := make(map[string]filters.Game, len(mq.Servers)) for _, h := range mq.Servers { hg[h] = filter.Game } data.HostsGames = hg // Order of retrieval is by amount of work that must be done (generally 1, 2, 3) // 1. rules (request chal #, recv chal #, req rules, recv rules) // games with multi-packet A2S_RULES replies do the most work; otherwise 1 = 2, 3 // 2. players (request chal #, recv chal #, req players, recv players) // 3. info: just request info & receive info // Note: some servers (i.e. new beta games) don't have all 3 of AS2_RULES/PLAYER/INFO if !filter.Game.IgnoreRules { data.Rules = batchRuleQuery(mq.Servers) } if !filter.Game.IgnorePlayers { data.Players = batchPlayerQuery(mq.Servers) } if !filter.Game.IgnoreInfo { data.Info = batchInfoQuery(mq.Servers) } serverlist, err := buildServerList(data, true) if err != nil { return nil, logger.LogAppError(err) } if config.Config.DebugConfig.EnableServerDump { if err := dumpServersToDisk(filter.Game.Name, serverlist); err != nil { logger.LogAppError(err) } } return serverlist, nil }
// Query retrieves the server information for a given set of host to game pairs // and returns it in a format that is presented to the API. It takes a map consisting // of host(s) and their corresponding game names (i.e: k:127.0.0.1:27960, v:"QuakeLive") func Query(hostsgames map[string]string) (*models.APIServerList, error) { hg := make(map[string]filters.Game, len(hostsgames)) needsPlayers := make([]string, len(hostsgames)) needsRules := make([]string, len(hostsgames)) needsInfo := make([]string, len(hostsgames)) for host, game := range hostsgames { fg := filters.GetGameByName(game) hg[host] = fg if !fg.IgnoreRules { needsRules = append(needsRules, host) } if !fg.IgnorePlayers { needsPlayers = append(needsPlayers, host) } if !fg.IgnoreInfo { needsInfo = append(needsInfo, host) } } data := a2sData{ HostsGames: hg, Info: batchInfoQuery(needsInfo), Rules: batchRuleQuery(needsRules), Players: batchPlayerQuery(needsPlayers), } sl, err := buildServerList(data, true) if err != nil { return models.GetDefaultServerList(), logger.LogAppError(err) } return sl, nil }
func verifyServerDbPath() error { if err := util.CreateDirectory(constants.DbDirectory); err != nil { logger.LogAppError(err) panic(fmt.Sprintf("Unable to create database directory %s: %s", constants.DbDirectory, err)) } if err := createServerDBtable(constants.GetServerDBPath()); err != nil { logger.LogAppErrorf("Unable to verify database path: %s", err) panic("Unable to verify database path") } return nil }
// DirectQuery allows a user to query any host even if it is not in the internal // server ID database. It is primarily intended for testing as it has two main // issues: 1) obvious security implications, 2) determining which game a user- // supplied host represents rests on potentially unreliable assumptions, which if // not true would cause games with incomplete support for all three A2S queries // (e.g. Reflex) to always fail. A production environment should use Query() instead. func DirectQuery(hosts []string) (*models.APIServerList, error) { hg := make(map[string]filters.Game, len(hosts)) // Try to account for the fact that we can't determine the game ahead of time // for user-specified direct host queries -- a number of assumptions: // (1) A2S_INFO for game/host, (2) extra data A2S_INFO flag & field w/ appid, //(3) game has been defined in game.go with the correct AppID and A2S ignore flags info := batchInfoQuery(hosts) needsRules := make([]string, len(hosts)) needsPlayers := make([]string, len(hosts)) for _, h := range hosts { logger.WriteDebug("direct query for %s. will try to figure out needed queries", h) if (info[h] != models.SteamServerInfo{}) { logger.WriteDebug("A2S_INFO not empty. got gameid: %d", info[h].ExtraData.GameID) fg := filters.GetGameByAppID(info[h].ExtraData.GameID) hg[h] = fg if !fg.IgnoreRules { logger.WriteDebug("based on game %s for %s, will need to get A2S_RULES", fg.Name, h) needsRules = append(needsRules, h) } if !fg.IgnorePlayers { logger.WriteDebug("based on game %s for %s, will need to get A2S_PLAYERS", fg.Name, h) needsPlayers = append(needsPlayers, h) } } else { logger.WriteDebug("A2S_INFO is nil. game will be unspecified; results may vary") hg[h] = filters.GameUnspecified } } data := a2sData{ HostsGames: hg, Info: info, Rules: batchRuleQuery(needsRules), Players: batchPlayerQuery(needsPlayers), } sl, err := buildServerList(data, true) if err != nil { return models.GetDefaultServerList(), logger.LogAppError(err) } return sl, nil }
// OpenCountryDB opens the country lookup database for reading. The caller of // this function will be responsinble for calling .Close(). func OpenCountryDB() (*CDB, error) { // Note: the caller of this function needs to handle .Close() conn, err := maxminddb.Open(constants.CountryDbFilePath) if err != nil { dir := "build/nix" if runtime.GOOS == "windows" { dir = "build\\win" } logger.LogAppError(err) panic( fmt.Sprintf( `Unable to open country database! Use the get_countrydb script in the %s directory to get the country DB file, or download it from: http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz and extract the "GeoLite2-City.mmdb" file into a directory called "db" in the same directory as the a2sapi executable. Error: %s`, dir, err)) } return &CDB{db: conn}, nil }
// Start listening for and responding to HTTP requests via the web server. Panics // if unable to start. func Start(runSilent bool) { r := newRouter() if !runSilent { printStartInfo() } logger.LogAppInfo("Starting HTTP server on port %d", config.Config.WebConfig.APIWebPort) srv := http.Server{ Addr: fmt.Sprintf(":%d", config.Config.WebConfig.APIWebPort), Handler: r, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, MaxHeaderBytes: 1 << 20} err := srv.ListenAndServe() if err != nil { logger.LogAppError(err) panic(fmt.Sprintf("Unable to start HTTP server, error: %s\n", err)) } }