Example #1
0
// Provide case-insensitive matching for URL paths and query strings
func pathQStrToLowerMatcherFunc(router *mux.Router,
	routepath string, querystrings []querystring,
	requiredQsCount int) func(req *http.Request,
	rt *mux.RouteMatch) bool {
	return func(req *http.Request, rt *mux.RouteMatch) bool {
		pathok, qstrok := false, false
		// case-insensitive paths
		if strings.HasPrefix(strings.ToLower(req.URL.Path), strings.ToLower(routepath)) {
			logger.WriteDebug("PATH: %s matches route path: %s", req.URL.Path, routepath)
			pathok = true
		}
		//case-insensitive query strings
		// not all API routes will make use of query strings
		if len(querystrings) == 0 {
			qstrok = true
		} else {
			qry := req.URL.Query()
			truecount := 0
			for key := range qry {
				logger.WriteDebug("URL query string key is: %s", key)
				for _, qs := range querystrings {
					if strings.EqualFold(key, qs.name) && qs.required {
						logger.WriteDebug("KEY: %s matches query string: %s", key, qs.name)
						truecount++
						break
					}
				}
			}
			if truecount == requiredQsCount {
				qstrok = true
			}
		}
		return pathok && qstrok
	}
}
Example #2
0
// GetIDsAPIQuery Retrieves the server ID numbers, hosts, and game name for a given
// set of hosts (represented by query string values) from the server database
// file in response to a query from the API. Sends the results over a DbServerID
// channel for consumption.
func (sdb *SDB) GetIDsAPIQuery(result chan *models.DbServerID, hosts []string) {
	m := &models.DbServerID{}
	for _, h := range hosts {
		logger.WriteDebug("DB: GetIDsAPIQuery, host: %s", h)
		rows, err := sdb.db.Query(
			"SELECT server_id, host, game FROM servers WHERE host LIKE ?",
			fmt.Sprintf("%%%s%%", h))
		if err != nil {
			logger.LogAppErrorf(
				"GetIDsAPIQuery: Error querying database to retrieve ID for host %s: %s",
				h, err)
			return
		}
		defer rows.Close()
		var id int64
		host, game := "", ""

		for rows.Next() {
			sid := models.DbServer{}
			if err := rows.Scan(&id, &host, &game); err != nil {
				logger.LogAppErrorf(
					"GetIDsAPIQuery: Error querying database to retrieve ID for host %s: %s",
					h, err)
				return
			}
			sid.ID = id
			sid.Host = host
			sid.Game = game
			m.Servers = append(m.Servers, sid)
		}
	}
	m.ServerCount = len(m.Servers)
	result <- m
}
Example #3
0
// 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
}
Example #4
0
func queryServerAddrs(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	if !config.Config.WebConfig.AllowDirectUserQueries {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprintf(w,
			`{"error": {"code": 400,"message": "Direct server queries are disabled. Use the %s parameter."}}`,
			qsQueryServerIDs)
		return
	}
	addresses := getQStringValues(r.URL.Query(), qsQueryServerAddrs)
	logger.WriteDebug("addresses length: %d", len(addresses))
	logger.WriteDebug("addresses are: %s", addresses)

	if addresses == nil {
		w.WriteHeader(http.StatusOK)
		logger.WriteDebug("queryServerAddr: Got empty address query. Ignoring.")
		writeJSONResponse(w, models.GetDefaultServerList())
		return
	}

	var parsedaddresses []string
	for _, addr := range addresses {
		host, err := net.ResolveTCPAddr("tcp4", addr)
		if err != nil {
			continue
		}
		parsedaddresses = append(parsedaddresses, fmt.Sprintf("%s:%d", host.IP, host.Port))
	}

	if len(parsedaddresses) == 0 {
		w.WriteHeader(http.StatusOK)
		logger.WriteDebug("queryServerAddr: No valid addresses for query. Ignoring.")
		writeJSONResponse(w, models.GetDefaultServerList())
		return
	}

	if len(parsedaddresses) > config.Config.WebConfig.MaximumHostsPerAPIQuery {
		logger.WriteDebug("Maximum number of allowed API query hosts exceeded, truncating")
		parsedaddresses = parsedaddresses[:config.Config.WebConfig.MaximumHostsPerAPIQuery]
	}
	queryServerAddrRetriever(w, parsedaddresses)
}
Example #5
0
func queryServerIDs(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	ids := getQStringValues(r.URL.Query(), qsQueryServerIDs)
	logger.WriteDebug("queryServerID: ids length: %d", len(ids))
	logger.WriteDebug("queryServerID: ids are: %s", ids)

	if ids == nil {
		w.WriteHeader(http.StatusOK)
		logger.WriteDebug("queryServerID: Got empty query. Ignoring.")
		writeJSONResponse(w, models.GetDefaultServerList())
		return
	}
	if len(ids) > config.Config.WebConfig.MaximumHostsPerAPIQuery {
		logger.WriteDebug("Maximum number of allowed API query hosts exceeded, truncating")
		ids = ids[:config.Config.WebConfig.MaximumHostsPerAPIQuery]
	}

	queryServerIDRetriever(w, ids)
}
Example #6
0
// StartMasterRetrieval starts a timed retrieval of servers specified by a given
// filter from the Steam Master server after an initial delay of initialDelay
// seconds. It retrieves the list every timeBetweenQueries seconds thereafter.
// A bool can be sent to the stop channel to cancel all timed retrievals.
func StartMasterRetrieval(stop chan bool, filter filters.Filter,
	initialDelay int, timeBetweenQueries int) {
	retrticker := time.NewTicker(time.Duration(timeBetweenQueries) * time.Second)

	logger.WriteDebug(
		"Waiting %d seconds before grabbing %s servers. Will retrieve servers every %d secs afterwards.", initialDelay, filter.Game.Name, timeBetweenQueries)

	logger.LogAppInfo(
		"Waiting %d seconds before grabbing %s servers from master. Will retrieve every %d secs afterwards.", initialDelay, filter.Game.Name, timeBetweenQueries)

	firstretrieval := time.NewTimer(time.Duration(initialDelay) * time.Second)
	<-firstretrieval.C
	logger.WriteDebug("Starting first retrieval of %s servers from master.",
		filter.Game.Name)
	sl, err := retrieve(filter)
	if err != nil {
		logger.LogAppErrorf("Error when performing timed master retrieval: %s", err)
	}
	models.MasterList = sl

	for {
		select {
		case <-retrticker.C:
			go func(filters.Filter) {
				logger.WriteDebug("%s: Starting %s master server query", time.Now().Format(
					"Mon Jan 2 15:04:05 2006 EST"), filter.Game.Name)
				logger.LogAppInfo("%s: Starting %s master server query", time.Now().Format(
					"Mon Jan 2 15:04:05 2006 EST"), filter.Game.Name)
				sl, err := retrieve(filter)
				if err != nil {
					logger.LogAppErrorf("Error when performing timed master retrieval: %s",
						err)
				}
				models.MasterList = sl
			}(filter)
		case <-stop:
			retrticker.Stop()
			return
		}
	}
}
Example #7
0
func getServerIDs(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	hosts := getQStringValues(r.URL.Query(), qsGetServerIDs)
	for _, v := range hosts {
		logger.WriteDebug("host slice values: %s", v)
		// basically require at least 2 octets
		if len(v) < 4 {
			w.WriteHeader(http.StatusBadRequest)
			writeJSONResponse(w, models.GetDefaultServerID())
			return
		}
	}
	getServerIDRetriever(w, hosts)
}
Example #8
0
func getServers(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	var asl *models.APIServerList

	if config.Config.DebugConfig.ServerDumpFileAsMasterList {
		asl = useDumpFileAsMasterList(constants.DumpFileFullPath(
			config.Config.DebugConfig.ServerDumpFilename))
	} else {
		asl = models.MasterList
	}
	// Empty (i.e. during first retrieval/startup)
	if asl == nil {
		writeJSONResponse(w, models.GetDefaultServerList())
		return
	}
	srvfilters := getSrvFilterFromQString(r.URL.Query(), getServersQueryStrings)
	logger.WriteDebug("server list will be filtered with: %v", srvfilters)
	list := filterServers(srvfilters, asl)
	writeJSONResponse(w, list)
}
Example #9
0
func getServersWeb(filter filters.Filter) ([]string, error) {
	var fsl []string
	for _, f := range filter.Filters {
		fsl = append(fsl, string(f))
	}
	filterStr := strings.Join(fsl, "")
	response, err := http.Get(steamWebAPIURL(config.Config.SteamConfig.SteamWebAPIKey, filterStr,
		config.Config.SteamConfig.MaximumHostsToReceive))
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	var webAPIResponseModel webGameServerList
	var servers []string
	apiResult := json.NewDecoder(response.Body)
	if err := apiResult.Decode(&webAPIResponseModel); err != nil {
		logger.WriteDebug("Error decoding Steam Web API response: %s", err)
		return nil, err
	}
	for _, server := range webAPIResponseModel.Response.Servers {
		servers = append(servers, server.Addr)
	}
	return servers, nil
}
Example #10
0
func getServers(filter filters.Filter) ([]string, error) {
	maxHosts := config.Config.SteamConfig.MaximumHostsToReceive
	var serverlist []string
	var c net.Conn
	var err error
	retrieved := 0
	addr := "0.0.0.0:0"

	c, err = net.DialTimeout("udp", masterServerHost,
		time.Duration(QueryTimeout)*time.Second)
	if err != nil {
		logger.LogSteamError(ErrHostConnection(err.Error()))
		return nil, ErrHostConnection(err.Error())
	}

	defer c.Close()
	c.SetDeadline(time.Now().Add(time.Duration(QueryTimeout) * time.Second))

	for {
		s, err := queryMasterServer(c, addr, filter)
		if err != nil {
			// usually timeout - Valve throttles >30 UDP packets (>6930 servers) per min
			logger.WriteDebug("Master query error, likely due to Valve throttle/timeout :%s",
				err)
			break
		}
		// get hosts:ports beginning after header (0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x0A)
		ips, total, err := extractHosts(s[6:])
		if err != nil {
			return nil, logger.LogAppErrorf("Error when extracting addresses: %s",
				err)
		}
		retrieved = retrieved + total
		if retrieved >= maxHosts {
			logger.LogSteamInfo("Max host limit of %d reached!", maxHosts)
			logger.WriteDebug("Max host limit of %d reached!", maxHosts)
			break
		}
		logger.LogSteamInfo("%d hosts retrieved so far from master.", retrieved)
		logger.WriteDebug("%d hosts retrieved so far from master.", retrieved)
		for _, ip := range ips {
			serverlist = append(serverlist, ip)
		}

		if (serverlist[len(serverlist)-1]) != "0.0.0.0:0" {
			logger.LogSteamInfo("More hosts need to be retrieved. Last IP was: %s",
				serverlist[len(serverlist)-1])
			logger.WriteDebug("More hosts need to be retrieved. Last IP was: %s",
				serverlist[len(serverlist)-1])
			addr = serverlist[len(serverlist)-1]
		} else {
			logger.LogSteamInfo("IP retrieval complete!")
			logger.WriteDebug("IP retrieval complete!")
			break
		}
	}
	// remove 0.0.0.0:0
	if len(serverlist) != 0 {
		if serverlist[len(serverlist)-1] == "0.0.0.0:0" {
			serverlist = serverlist[:len(serverlist)-1]
		}
	}
	return serverlist, nil
}
Example #11
0
func buildServerList(data a2sData, addtoServerDB bool) (*models.APIServerList,
	error) {
	// Cannot ignore all three requests
	for _, g := range data.HostsGames {
		if g.IgnoreInfo && g.IgnorePlayers && g.IgnoreRules {
			return nil, logger.LogAppErrorf("Cannot ignore all three A2S_ requests!")
		}
	}
	successcount := 0
	var success bool
	srvDBhosts := make(map[string]string, len(data.HostsGames))
	sl := &models.APIServerList{
		Servers:       make([]models.APIServer, 0),
		FailedServers: make([]string, 0),
	}

	for host, game := range data.HostsGames {
		info, iok := data.Info[host]
		players, pok := data.Players[host]
		if players == nil {
			// return empty array instead of nil pointers (null) in json
			players = make([]models.SteamPlayerInfo, 0)
		}
		rules, rok := data.Rules[host]
		success = iok && rok && pok

		if game.IgnoreInfo {
			success = pok && rok
		}
		if game.IgnorePlayers {
			success = iok && rok
		}
		if game.IgnoreRules {
			rules = make(map[string]string, 0)
			success = iok && pok
		}
		if game.IgnoreInfo && game.IgnorePlayers {
			success = rok
		}
		if game.IgnoreInfo && game.IgnoreRules {
			success = pok
		}
		if game.IgnorePlayers && game.IgnoreRules {
			success = iok
		}

		if success {
			srv := models.APIServer{
				Game:            game.Name,
				Players:         players,
				FilteredPlayers: removeBuggedPlayers(players),
				Rules:           rules,
				Info:            info,
			}
			// Gametype support: gametype can be found in rules, info, or not
			// at all depending on the game (currently just for QuakeLive & Reflex)
			srv.Info.GameTypeShort, srv.Info.GameTypeFull = getGameType(game, srv)

			ip, port, serr := net.SplitHostPort(host)
			if serr == nil {
				srv.IP = ip
				srv.Host = host
				p, perr := strconv.Atoi(port)
				if perr == nil {
					srv.Port = p
				}
				if !strings.EqualFold(game.Name, filters.GameUnspecified.String()) {
					srvDBhosts[host] = game.Name
				}
				loc := make(chan models.DbCountry, 1)
				go db.CountryDB.GetCountryInfo(loc, ip)
				srv.CountryInfo = <-loc
			}
			sl.Servers = append(sl.Servers, srv)
			successcount++
		} else {
			sl.FailedServers = append(sl.FailedServers, host)
		}
	}

	sl.RetrievedAt = time.Now().Format("Mon Jan 2 15:04:05 2006 EST")
	sl.RetrievedTimeStamp = time.Now().Unix()
	sl.ServerCount = len(sl.Servers)
	sl.FailedCount = len(sl.FailedServers)

	if len(srvDBhosts) != 0 {
		go db.ServerDB.AddServersToDB(srvDBhosts)
		sl.Servers = setServerIDsForList(sl.Servers)
	}

	logger.LogAppInfo(
		"Successfully queried (%d/%d) servers. %d timed out or otherwise failed.",
		successcount, len(data.HostsGames), sl.FailedCount)
	logger.WriteDebug("Server Queries: Successful: (%d/%d) servers\tFailed: %d servers",
		successcount, len(data.HostsGames), sl.FailedCount)
	return sl, nil
}