mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-06-18 21:29:20 -05:00
NAS: Rewrite HTTP handler
This commit is contained in:
parent
54dec238ba
commit
8804352d29
|
|
@ -1,14 +0,0 @@
|
|||
package nas
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
)
|
||||
|
||||
func acctcreate(r *Response, fields map[string]string) map[string]string {
|
||||
return map[string]string{
|
||||
"retry": "0",
|
||||
"returncd": "002",
|
||||
"userid": strconv.FormatInt(database.GetUniqueUserID(), 10),
|
||||
}
|
||||
}
|
||||
110
nas/auth.go
Normal file
110
nas/auth.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package nas
|
||||
|
||||
import (
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Failed to parse form")
|
||||
return
|
||||
}
|
||||
|
||||
fields := map[string]string{}
|
||||
for key, values := range r.PostForm {
|
||||
if len(values) != 1 {
|
||||
logging.Warn(moduleName, "Ignoring multiple POST form values:", aurora.Cyan(key).String()+":", aurora.Cyan(values))
|
||||
continue
|
||||
}
|
||||
|
||||
parsed, err := common.Base64DwcEncoding.DecodeString(values[0])
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid POST form value:", aurora.Cyan(key).String()+":", aurora.Cyan(values[0]))
|
||||
return
|
||||
}
|
||||
logging.Info(moduleName, aurora.Cyan(key).String()+":", aurora.Cyan(string(parsed)))
|
||||
fields[key] = string(parsed)
|
||||
}
|
||||
|
||||
action, ok := fields["action"]
|
||||
if !ok || action == "" {
|
||||
logging.Error(moduleName, "No action in form")
|
||||
return
|
||||
}
|
||||
|
||||
reply := map[string]string{}
|
||||
|
||||
switch action {
|
||||
case "login":
|
||||
reply = login(moduleName, fields)
|
||||
break
|
||||
|
||||
case "acctcreate":
|
||||
reply = acctcreate(moduleName, fields)
|
||||
break
|
||||
}
|
||||
|
||||
param := url.Values{}
|
||||
for key, value := range reply {
|
||||
param.Set(key, common.Base64DwcEncoding.EncodeToString([]byte(value)))
|
||||
}
|
||||
response := []byte(param.Encode())
|
||||
response = []byte(strings.Replace(string(response), "%2A", "*", -1))
|
||||
// DWC treats the response like a null terminated string
|
||||
response = append(response, 0x00)
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response)))
|
||||
w.Write(response)
|
||||
}
|
||||
|
||||
func acctcreate(moduleName string, fields map[string]string) map[string]string {
|
||||
return map[string]string{
|
||||
"retry": "0",
|
||||
"returncd": "002",
|
||||
"userid": strconv.FormatInt(database.GetUniqueUserID(), 10),
|
||||
}
|
||||
}
|
||||
|
||||
func login(moduleName string, fields map[string]string) map[string]string {
|
||||
param := map[string]string{
|
||||
"retry": "0",
|
||||
"locator": "gs.wiilink24.com",
|
||||
}
|
||||
|
||||
strUserId, ok := fields["userid"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No userid in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
userId, err := strconv.ParseInt(strUserId, 10, 64)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid userid string in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
gsbrcd, ok := fields["gsbrcd"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No gsbrcd in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
authToken, challenge := database.GenerateAuthToken(pool, ctx, userId, gsbrcd)
|
||||
|
||||
param["returncd"] = "001"
|
||||
param["challenge"] = challenge
|
||||
param["token"] = authToken
|
||||
return param
|
||||
}
|
||||
44
nas/login.go
44
nas/login.go
|
|
@ -1,44 +0,0 @@
|
|||
package nas
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"wwfc/database"
|
||||
"wwfc/logging"
|
||||
)
|
||||
|
||||
func login(r *Response, fields map[string]string) map[string]string {
|
||||
moduleName := "NAS:" + r.request.RemoteAddr
|
||||
|
||||
param := map[string]string{
|
||||
"retry": "0",
|
||||
"locator": "gamespy.com",
|
||||
}
|
||||
|
||||
strUserId, ok := fields["userid"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No userid in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
userId, err := strconv.ParseInt(strUserId, 10, 64)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid userid string in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
gsbrcd, ok := fields["gsbrcd"]
|
||||
if !ok {
|
||||
logging.Error(moduleName, "No gsbrcd in form")
|
||||
param["returncd"] = "103"
|
||||
return param
|
||||
}
|
||||
|
||||
authToken, challenge := database.GenerateAuthToken(pool, ctx, userId, gsbrcd)
|
||||
|
||||
param["returncd"] = "001"
|
||||
param["challenge"] = challenge
|
||||
param["token"] = authToken
|
||||
return param
|
||||
}
|
||||
66
nas/main.go
66
nas/main.go
|
|
@ -4,10 +4,16 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
"wwfc/nhttp"
|
||||
"wwfc/sake"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -32,20 +38,52 @@ func StartServer() {
|
|||
}
|
||||
|
||||
address := config.Address + ":" + config.Port
|
||||
r := NewRoute()
|
||||
ac := r.HandleGroup("ac")
|
||||
{
|
||||
ac.HandleAction("acctcreate", acctcreate)
|
||||
ac.HandleAction("login", login)
|
||||
}
|
||||
|
||||
// TODO: Hack lol
|
||||
p0 := r.HandleGroup("p0")
|
||||
{
|
||||
p0.HandleAction("acctcreate", getStage1)
|
||||
p0.HandleAction("login", getStage1)
|
||||
}
|
||||
|
||||
logging.Notice("NAS", "Starting HTTP server on", address)
|
||||
log.Fatal(nhttp.ListenAndServe(address, r.Handle()))
|
||||
log.Fatal(nhttp.ListenAndServe(address, http.HandlerFunc(handleRequest)))
|
||||
}
|
||||
|
||||
var regexSakeHost = regexp.MustCompile(`^([a-z\-]+\.)?sake\.gs\.`)
|
||||
var regexStage1URL = regexp.MustCompile(`^/p([0-9])$`)
|
||||
|
||||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: Move this to its own server
|
||||
// Check for *.sake.gs.* or sake.gs.*
|
||||
if regexSakeHost.MatchString(r.Host) {
|
||||
// Redirect to the sake server
|
||||
sake.HandleRequest(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Notice("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
moduleName := "NAS:" + r.RemoteAddr
|
||||
|
||||
if r.URL.String() == "/ac" {
|
||||
handleAuthRequest(moduleName, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Move this to its own server
|
||||
// Check for /payload
|
||||
if strings.HasPrefix(r.URL.String(), "/payload?") {
|
||||
handlePayloadRequest(moduleName, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /online
|
||||
if r.URL.String() == "/online" {
|
||||
returnOnlineStats(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Stage 1
|
||||
if match := regexStage1URL.FindStringSubmatch(r.URL.String()); match != nil {
|
||||
val, err := strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
downloadStage1(moduleName, w, r, val)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,17 @@ import (
|
|||
"wwfc/logging"
|
||||
)
|
||||
|
||||
func getStage1(r *Response, fields map[string]string) map[string]string {
|
||||
func downloadStage1(moduleName string, w http.ResponseWriter, r *http.Request, stage1Ver int) {
|
||||
dat, err := os.ReadFile("payload/stage1.bin")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.payload = append([]byte{0x01, 0x2C}, dat...)
|
||||
return map[string]string{}
|
||||
payload := append([]byte{0x01, 0x2C}, dat...)
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(payload)))
|
||||
w.Write(payload)
|
||||
}
|
||||
|
||||
func handlePayloadRequest(moduleName string, w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
1
nas/profanity.go
Normal file
1
nas/profanity.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package nas
|
||||
156
nas/route.go
156
nas/route.go
|
|
@ -1,156 +0,0 @@
|
|||
package nas
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/logging"
|
||||
"wwfc/sake"
|
||||
)
|
||||
|
||||
type Route struct {
|
||||
Actions []Action
|
||||
}
|
||||
|
||||
// Action contains information about how a specified action should be handled.
|
||||
type Action struct {
|
||||
ActionName string
|
||||
Callback func(*Response, map[string]string) map[string]string
|
||||
ServiceType string
|
||||
}
|
||||
|
||||
func NewRoute() Route {
|
||||
return Route{}
|
||||
}
|
||||
|
||||
// RoutingGroup defines a group of actions for a given service type.
|
||||
type RoutingGroup struct {
|
||||
Route *Route
|
||||
ServiceType string
|
||||
}
|
||||
|
||||
// HandleGroup returns a routing group type for the given service type.
|
||||
func (route *Route) HandleGroup(serviceType string) RoutingGroup {
|
||||
return RoutingGroup{
|
||||
Route: route,
|
||||
ServiceType: serviceType,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RoutingGroup) HandleAction(action string, function func(*Response, map[string]string) map[string]string) {
|
||||
r.Route.Actions = append(r.Route.Actions, Action{
|
||||
ActionName: action,
|
||||
Callback: function,
|
||||
ServiceType: r.ServiceType,
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
regexSakeURL = regexp.MustCompile(`^([a-z\-]+\.)?sake\.gs\.`)
|
||||
)
|
||||
|
||||
func (route *Route) Handle() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: Move this to its own server
|
||||
// Check for *.sake.gs.* or sake.gs.*
|
||||
if regexSakeURL.MatchString(r.Host) {
|
||||
// Redirect to the sake server
|
||||
sake.HandleRequest(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Notice("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||
moduleName := "NAS:" + r.RemoteAddr
|
||||
|
||||
// TODO: Move this to its own server
|
||||
// Check for /payload
|
||||
if strings.HasPrefix(r.URL.String(), "/payload") {
|
||||
handlePayloadRequest(moduleName, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for /online
|
||||
if strings.HasPrefix(r.URL.String(), "/online") {
|
||||
returnOnlineStats(w)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Failed to parse form")
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, "/") {
|
||||
logging.Error(moduleName, "Invalid URL")
|
||||
return
|
||||
}
|
||||
|
||||
path := r.URL.Path[1:]
|
||||
|
||||
fields := map[string]string{}
|
||||
for key, values := range r.PostForm {
|
||||
if len(values) != 1 {
|
||||
logging.Warn(moduleName, "Ignoring multiple POST form values:", aurora.Cyan(key).String()+":", aurora.Cyan(values))
|
||||
continue
|
||||
}
|
||||
|
||||
parsed, err := base64.StdEncoding.DecodeString(strings.Replace(values[0], "*", "=", -1))
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Invalid POST form value:", aurora.Cyan(key).String()+":", aurora.Cyan(values[0]))
|
||||
return
|
||||
}
|
||||
logging.Info(moduleName, aurora.Cyan(key).String()+":", aurora.Cyan(string(parsed)))
|
||||
fields[key] = string(parsed)
|
||||
}
|
||||
|
||||
actionName, ok := fields["action"]
|
||||
if !ok || actionName == "" {
|
||||
logging.Error(moduleName, "No action in form")
|
||||
return
|
||||
}
|
||||
|
||||
var action Action
|
||||
for _, _action := range route.Actions {
|
||||
if path == _action.ServiceType && actionName == _action.ActionName {
|
||||
action = _action
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we found an action
|
||||
if action.ActionName == "" && action.ServiceType == "" {
|
||||
logging.Error(moduleName, "No action for", aurora.Cyan(actionName))
|
||||
return
|
||||
}
|
||||
|
||||
response := NewResponse(&w, r)
|
||||
reply := action.Callback(response, fields)
|
||||
|
||||
if len(reply) != 0 {
|
||||
param := url.Values{}
|
||||
for key, value := range reply {
|
||||
param.Set(key, strings.Replace(base64.StdEncoding.EncodeToString([]byte(value)), "=", "*", -1))
|
||||
}
|
||||
response.payload = []byte(param.Encode())
|
||||
response.payload = []byte(strings.Replace(string(response.payload), "%2A", "*", -1))
|
||||
// DWC treats the response like a null terminated string
|
||||
response.payload = append(response.payload, 0x00)
|
||||
}
|
||||
|
||||
w.Header().Set("NODE", "wifiappe1")
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(response.payload)))
|
||||
w.Write(response.payload)
|
||||
})
|
||||
}
|
||||
|
||||
func NewResponse(w *http.ResponseWriter, r *http.Request) *Response {
|
||||
return &Response{
|
||||
request: r,
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package nas
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Response struct {
|
||||
request *http.Request
|
||||
writer *http.ResponseWriter
|
||||
payload []byte
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user