NAS: Rewrite HTTP handler

This commit is contained in:
mkwcat 2023-11-24 22:32:16 -05:00
parent 54dec238ba
commit 8804352d29
No known key found for this signature in database
GPG Key ID: 7A505679CE9E7AA9
8 changed files with 169 additions and 240 deletions

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
View File

@ -0,0 +1 @@
package nas

View File

@ -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,
}
}

View File

@ -1,9 +0,0 @@
package nas
import "net/http"
type Response struct {
request *http.Request
writer *http.ResponseWriter
payload []byte
}