Add proper registration response

This commit is contained in:
Spotlight 2020-08-20 22:57:04 -05:00
parent de5c682dd5
commit 5cbf9f81a8
No known key found for this signature in database
GPG Key ID: 874AA355B3209BDC
6 changed files with 124 additions and 23 deletions

View File

@ -36,16 +36,19 @@ SET time_zone = "+00:00";
--
CREATE TABLE `userbase` (
`DeviceId` int(10) UNSIGNED ZEROFILL NOT NULL,
`DeviceToken` varchar(21) NOT NULL,
`AccountId` int(9) UNSIGNED ZEROFILL NOT NULL,
`Region` varchar(2) DEFAULT NULL,
`Country` varchar(2) DEFAULT NULL,
`Language` varchar(2) DEFAULT NULL,
`SerialNo` varchar(11) DEFAULT NULL,
`DeviceCode` int(16) UNSIGNED ZEROFILL NOT NULL
`DeviceId` varchar(10) NOT NULL,
`DeviceToken` varchar(64) NOT NULL COMMENT 'This token should be considered a secret, so after generation only the sha256sum of the md5 the Wii sends is inserted.',
`AccountId` varchar(9) NOT NULL,
`Region` varchar(2) NOT NULL,
`Country` varchar(2) NOT NULL,
`Language` varchar(2) NOT NULL,
`SerialNo` varchar(11) NOT NULL,
`DeviceCode` varchar(16) NOT NULL,
PRIMARY KEY (`AccountId`),
UNIQUE KEY `AccountId` (`AccountId`),
UNIQUE KEY `userbase_DeviceId_uindex` (`DeviceId`),
UNIQUE KEY `userbase_DeviceToken_uindex` (`DeviceToken`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/morenatsu-net/WiiSOAP
go 1.12
require (
github.com/RiiConnect24/wiino v0.0.0-20200719211820-910fed2fa406
github.com/antchfx/xmlquery v1.2.4
github.com/go-sql-driver/mysql v1.5.0
)

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/RiiConnect24/wiino v0.0.0-20200719211820-910fed2fa406 h1:io4hcjlXc3gsbSLtIR7Xd7nK+Tngl3hHrPVlI7aZfR0=
github.com/RiiConnect24/wiino v0.0.0-20200719211820-910fed2fa406/go.mod h1:BmIQ5QOpoum6rxYqqAjdpwwdfoQeKVi1xnxeEU+56ro=
github.com/antchfx/xmlquery v1.2.4 h1:T/SH1bYdzdjTMoz2RgsfVKbM5uWh3gjDYYepFqQmFv4=
github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM=
github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0=

95
ias.go
View File

@ -18,11 +18,32 @@
package main
import (
"crypto/md5"
sha2562 "crypto/sha256"
"errors"
"fmt"
"github.com/RiiConnect24/wiino/golang"
"github.com/antchfx/xmlquery"
"github.com/go-sql-driver/mysql"
"log"
"math/rand"
"strconv"
)
func iasHandler(e Envelope, doc *xmlquery.Node) (bool, string) {
// All IAS-related functions should contain these keys.
region, err := getKey(doc, "Region")
if err != nil {
return e.ReturnError(5, "not good enough for me. ;3", err)
}
country, err := getKey(doc, "Country")
if err != nil {
return e.ReturnError(5, "not good enough for me. ;3", err)
}
language, err := getKey(doc, "Language")
if err != nil {
return e.ReturnError(5, "not good enough for me. ;3", err)
}
// All actions below are for IAS-related functions.
switch e.Action() {
@ -50,14 +71,15 @@ func iasHandler(e Envelope, doc *xmlquery.Node) (bool, string) {
break
case "GetRegistrationInfo":
reason := "how dirty. ;3"
accountId, err := getKey(doc, "AccountId")
if err != nil {
return e.ReturnError(7, "how dirty. ;3", err)
return e.ReturnError(7, reason, err)
}
country, err := getKey(doc, "Country")
deviceCode, err := getKey(doc, "DeviceCode")
if err != nil {
return e.ReturnError(7, "how dirty. ;3", err)
return e.ReturnError(7, reason, err)
}
fmt.Println("The request is valid! Responding...")
@ -66,29 +88,82 @@ func iasHandler(e Envelope, doc *xmlquery.Node) (bool, string) {
e.AddKVNode("DeviceTokenExpired", "false")
e.AddKVNode("Country", country)
e.AddKVNode("ExtAccountId", "")
e.AddKVNode("DeviceCode", "0000000000000000")
e.AddKVNode("DeviceCode", deviceCode)
e.AddKVNode("DeviceStatus", "R")
// This _must_ be POINTS.
e.AddKVNode("Currency", "POINTS")
break
case "Register":
accountId, err := getKey(doc, "AccountId")
reason := "disgustingly invalid. ;3"
deviceCode, err := getKey(doc, "DeviceCode")
if err != nil {
return e.ReturnError(8, "disgustingly invalid. ;3", err)
return e.ReturnError(7, reason, err)
}
country, err := getKey(doc, "Country")
registerRegion, err := getKey(doc, "RegisterRegion")
if err != nil {
return e.ReturnError(8, "disgustingly invalid. ;3", err)
return e.ReturnError(7, reason, err)
}
if registerRegion != region {
return e.ReturnError(7, reason, errors.New("region does not match registration region"))
}
serialNo, err := getKey(doc, "SerialNumber")
if err != nil {
return e.ReturnError(7, reason, err)
}
// Validate given friend code.
userId, err := strconv.ParseUint(deviceCode, 10, 64)
if err != nil {
return e.ReturnError(7, reason, err)
}
if wiino.NWC24CheckUserID(userId) != 0 {
return e.ReturnError(7, reason, err)
}
// Generate a random 9-digit number, padding zeros as necessary.
accountId := fmt.Sprintf("%9d", rand.Intn(999999999))
// This is where it gets hairy.
// Generate a device token, 21 characters...
deviceToken := RandString(21)
// ...and then its md5, because the Wii sends this...
md5DeviceToken := fmt.Sprintf("%x", md5.Sum([]byte(deviceToken)))
// ...and then the sha256 of that md5.
// We'll store this in our database, as storing the md5 itself is effectively the token.
// It would not be good for security to directly store the token either.
// This is the hash of the md5 represented as a string, not individual byte values.
doublyHashedDeviceToken := fmt.Sprintf("%x", sha2562.Sum256([]byte(md5DeviceToken)))
// Insert all of our obtained values to the database..
stmt, err := db.Prepare(`INSERT INTO wiisoap.userbase (DeviceId, DeviceToken, AccountId, Region, Country, Language, SerialNo, DeviceCode) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
log.Printf("error preparing statement: %v\n", err)
return e.ReturnError(7, reason, errors.New("failed to prepare statement"))
}
_, err = stmt.Exec(e.DeviceId(), doublyHashedDeviceToken, accountId, region, country, language, serialNo, deviceCode)
if err != nil {
// It's okay if this isn't a MySQL error, as perhaps other issues have come in.
if driverErr, ok := err.(*mysql.MySQLError); ok {
if driverErr.Number == 1062 {
return e.ReturnError(7, reason, errors.New("user already exists"))
}
}
log.Printf("error executing statement: %v\n", err)
return e.ReturnError(7, reason, errors.New("failed to execute db operation"))
}
fmt.Println("The request is valid! Responding...")
e.AddKVNode("AccountId", accountId)
e.AddKVNode("DeviceToken", "00000000")
e.AddKVNode("DeviceToken", deviceToken)
e.AddKVNode("DeviceTokenExpired", "false")
e.AddKVNode("Country", country)
// Optionally, one can send back DeviceCode and ExtAccountId to update on device.
// We send these back as-is regardless.
e.AddKVNode("ExtAccountId", "")
e.AddKVNode("DeviceCode", "0000000000000000")
e.AddKVNode("DeviceCode", deviceCode)
break
case "Unregister":

View File

@ -29,10 +29,13 @@ import (
)
const (
// Any given challenge must be 10 characters or less (challenge.length > 0xb)
// SharedChallenge represents a static value to this nonsensical challenge response system.
// The given challenge must be 11 characters or less. Contents do not matter.
SharedChallenge = "NintyWhyPls"
)
var db *sql.DB
// checkError makes error handling not as ugly and inefficient.
func checkError(err error) {
if err != nil {
@ -54,7 +57,7 @@ func main() {
fmt.Println("[i] Initializing core...")
// Start SQL.
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", CON.SQLUser, CON.SQLPass, CON.SQLAddress, CON.SQLDB))
db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", CON.SQLUser, CON.SQLPass, CON.SQLAddress, CON.SQLDB))
checkError(err)
// Close SQL after everything else is done.

View File

@ -23,6 +23,7 @@ import (
"fmt"
"github.com/antchfx/xmlquery"
"io"
"math/rand"
"regexp"
"time"
)
@ -75,7 +76,12 @@ func (e *Envelope) Timestamp() string {
return e.Body.Response.TimeStamp
}
// obtainCommon interprets a given node, and updates the envelope with common key values.
// DeviceId returns the Device ID for this request.
func (e *Envelope) DeviceId() string {
return e.Body.Response.DeviceId
}
// ObtainCommon interprets a given node, and updates the envelope with common key values.
func (e *Envelope) ObtainCommon(doc *xmlquery.Node) error {
var err error
@ -130,7 +136,7 @@ func (e *Envelope) ReturnSuccess() (bool, string) {
return e.becomeXML(true)
}
// formatError returns a standard SOAP response with an error code.
// ReturnError returns a standard SOAP response with an error code.
func (e *Envelope) ReturnError(errorCode int, reason string, err error) (bool, string) {
e.Body.Response.ErrorCode = errorCode
@ -180,3 +186,14 @@ func getKey(doc *xmlquery.Node, key string) (string, error) {
return node.InnerText(), nil
}
}
// Derived from https://stackoverflow.com/a/31832326, adding numbers
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandString(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}