From ce7afd94ec9fd99fdc463a5c97f470a554755f68 Mon Sep 17 00:00:00 2001 From: Spotlight Date: Tue, 28 Jul 2020 03:47:14 -0500 Subject: [PATCH] Split handlers into their individual files Eventually, this will allow for us to cover further namespaces, such as CCS or NUS proper. --- ecs.go | 189 +++++++++++++++++++++++++++++++++ ias.go | 179 +++++++++++++++++++++++++++++++ main.go | 318 ++----------------------------------------------------- utils.go | 37 +++++++ 4 files changed, 412 insertions(+), 311 deletions(-) create mode 100644 ecs.go create mode 100644 ias.go create mode 100644 utils.go diff --git a/ecs.go b/ecs.go new file mode 100644 index 0000000..060177b --- /dev/null +++ b/ecs.go @@ -0,0 +1,189 @@ +// Copyright (C) 2018-2020 CornierKhan1 +// +// WiiSOAP is SOAP Server Software, designed specifically to handle Wii Shop Channel SOAP. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "time" +) + +func ecsHandler(w http.ResponseWriter, r *http.Request) { + // Figure out the action to handle via header. + action := r.Header.Get("SOAPAction") + action = parseAction(action, "ecs") + + // Get a sexy new timestamp to use. + timestampNano := strconv.FormatInt(time.Now().UTC().Unix(), 10) + timestamp := timestampNano + "000" + + fmt.Println("[!] Incoming ECS request.") + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body...", http.StatusInternalServerError) + } + + // The switch converts the HTTP Body of the request into a string. There is no need to convert the cases to byte format. + switch action { + // TODO: Make the case functions cleaner. (e.g. Should the response be a variable?) + // TODO: Update the responses so that they query the SQL Database for the proper information (e.g. Device Code, Token, etc). + + case "CheckDeviceStatus": + fmt.Println("CDS.") + CDS := CDS{} + if err = xml.Unmarshal(body, &CDS); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "You need to POST some SOAP from WSC if you wanna get some, honey. ;3") + return + } + fmt.Println(CDS) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + + 2018 + POINTS + + 0 + %s + %s + + +`, CDS.Version, CDS.DeviceID, CDS.MessageID, timestamp, timestamp, timestamp) + + case "NotifiedETicketsSynced": + fmt.Println("NETS") + NETS := NETS{} + if err = xml.Unmarshal(body, &NETS); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "This is a disgusting request, but 20 dollars is 20 dollars. ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(NETS) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + + +`, NETS.Version, NETS.DeviceID, NETS.MessageID, timestamp) + + case "ListETickets": + fmt.Println("LET") + LET := LET{} + if err = xml.Unmarshal(body, &LET); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "that's all you got for me? ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(LET) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + 0 + %s + %s + + +`, LET.Version, LET.DeviceID, LET.MessageID, timestamp, timestamp, timestamp) + + case "PurchaseTitle": + fmt.Println("PT") + PT := PT{} + if err = xml.Unmarshal(body, &PT); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "if you wanna fun time, its gonna cost ya extra sweetie. ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(PT) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + + 2018 + POINTS + + + 00000000 + %s + PURCHGAME + + %s + 00000000 + 00000000 + 00000000 + 00000000 + + +`, PT.Version, PT.DeviceID, PT.MessageID, timestamp, timestamp, timestamp) + + default: + fmt.Fprintf(w, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer.") + return + } + + fmt.Println("Delivered response!") + + // TODO: Add NUS and CAS SOAP to the case list. + fmt.Println("[!] End of ECS Request.\n") +} diff --git a/ias.go b/ias.go new file mode 100644 index 0000000..9cd9cb9 --- /dev/null +++ b/ias.go @@ -0,0 +1,179 @@ +// Copyright (C) 2018-2020 CornierKhan1 +// +// WiiSOAP is SOAP Server Software, designed specifically to handle Wii Shop Channel SOAP. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "time" +) + +func iasHandler(w http.ResponseWriter, r *http.Request) { + // Figure out the action to handle via header. + action := r.Header.Get("SOAPAction") + action = parseAction(action, "ias") + + // Get a sexy new timestamp to use. + timestampNano := strconv.FormatInt(time.Now().UTC().Unix(), 10) + timestamp := timestampNano + "000" + + fmt.Println("[!] Incoming IAS request.") + body, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error reading request body...", http.StatusInternalServerError) + } + + // The switch converts the HTTP Body of the request into a string. There is no need to convert the cases to byte format. + switch action { + // TODO: Make the case functions cleaner. (e.g. Should the response be a variable?) + // TODO: Update the responses so that they query the SQL Database for the proper information (e.g. Device Code, Token, etc). + + case "CheckRegistration": + fmt.Println("CR.") + CR := CR{} + if err = xml.Unmarshal(body, &CR); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "not good enough for me. ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(CR) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + %s + R + + +`, CR.Version, CR.DeviceID, CR.DeviceID, timestamp, CR.SerialNo) + + case "GetRegistrationInfo": + fmt.Println("GRI.") + GRI := GRI{} + if err = xml.Unmarshal(body, &GRI); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "how dirty. ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(GRI) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + %s + 00000000 + false + %s + + 0000000000000000 + R + POINTS + + +`, GRI.Version, GRI.DeviceID, GRI.MessageID, timestamp, GRI.AccountID, GRI.Country) + + case "Register": + fmt.Println("REG.") + REG := REG{} + if err = xml.Unmarshal(body, ®); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "disgustingly invalid. ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(REG) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + %s + 00000000 + %s + + 00000000 + + +`, REG.Version, REG.DeviceID, REG.MessageID, timestamp, REG.AccountID, REG.Country) + + case "Unregister": + fmt.Println("UNR.") + UNR := UNR{} + if err = xml.Unmarshal(body, &UNR); err != nil { + fmt.Println("...or not. Bad or incomplete request. (End processing.)") + fmt.Fprint(w, "how abnormal... ;3") + fmt.Printf("Error: %v", err) + return + } + fmt.Println(UNR) + fmt.Println("The request is valid! Responding...") + fmt.Fprintf(w, ` + + + + %s + %s + %s + %s + 0 + false + + +`, UNR.Version, UNR.DeviceID, UNR.MessageID, timestamp) + + default: + fmt.Fprintf(w, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer.") + return + } + + fmt.Println("Delivered response!") + + // TODO: Add NUS and CAS SOAP to the case list. + fmt.Println("[!] End of IAS Request.\n") +} diff --git a/main.go b/main.go index 2ea68cc..2908ec3 100644 --- a/main.go +++ b/main.go @@ -24,9 +24,6 @@ import ( "io/ioutil" "log" "net/http" - "strconv" - "strings" - "time" _ "github.com/go-sql-driver/mysql" ) @@ -41,7 +38,7 @@ const ( // checkError makes error handling not as ugly and inefficient. func checkError(err error) { if err != nil { - log.Fatalf("WiiSOAP forgot how to drive and suddenly crashed! Reason: %s\n", err.Error()) + log.Fatalf("WiiSOAP forgot how to drive and suddenly crashed! Reason: %v\n", err) } } @@ -69,313 +66,12 @@ func main() { // Start the HTTP server. fmt.Printf("Starting HTTP connection (%s)...\nNot using the usual port for HTTP?\nBe sure to use a proxy, otherwise the Wii can't connect!\n", CON.Address) - http.HandleFunc("/ecs/services/ECommerceSOAP", handler) // each request calls handler + + // These following endpoints don't have to match what the official WSC have. + // However, semantically, it feels proper. + http.HandleFunc("/ecs/services/ECommerceSOAP", ecsHandler) // For ECS operations + http.HandleFunc("/ias/services/IdentityAuthenticationSOAP", iasHandler) // For IAS operations log.Fatal(http.ListenAndServe(CON.Address, nil)) - // From here on out, all special cool things should go into the handler function. -} - -func handler(w http.ResponseWriter, r *http.Request) { - // Figure out the action to handle via header. - action := r.Header.Get("SOAPAction") - action = parseAction(action, "ecs") - - // Get a sexy new timestamp to use. - timestampnano := strconv.FormatInt(time.Now().UTC().Unix(), 10) - timestamp := timestampnano + "000" - - fmt.Println("[!] Incoming request.") - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Error reading request body...", http.StatusInternalServerError) - } - - // The switch converts the HTTP Body of the request into a string. There is no need to convert the cases to byte format. - switch action { - // TODO: Make the case functions cleaner. (e.g. Should the response be a variable?) - // TODO: Update the responses so that they query the SQL Database for the proper information (e.g. Device Code, Token, etc). - - case "CheckDeviceStatus": - fmt.Println("CDS.") - CDS := CDS{} - if err = xml.Unmarshal(body, &CDS); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "You need to POST some SOAP from WSC if you wanna get some, honey. ;3") - return - } - fmt.Println(CDS) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - - 2018 - POINTS - - 0 - %s - %s - - -`, CDS.Version, CDS.DeviceID, CDS.MessageID, timestamp, timestamp, timestamp) - fmt.Println("Delivered response!") - - case "NotifiedETicketsSynced": - fmt.Println("NETS") - NETS := NETS{} - if err = xml.Unmarshal(body, &NETS); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "This is a disgusting request, but 20 dollars is 20 dollars. ;3") - fmt.Printf("error: %v", err) - return - } - fmt.Println(NETS) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - - -`, NETS.Version, NETS.DeviceID, NETS.MessageID, timestamp) - fmt.Println("Delivered response!") - - case "ListETickets": - fmt.Println("LET") - LET := LET{} - if err = xml.Unmarshal(body, &LET); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "This is a disgusting request, but 20 dollars is 20 dollars. ;3") - fmt.Printf("error: %v", err) - return - } - fmt.Println(LET) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - 0 - %s - %s - - -`, LET.Version, LET.DeviceID, LET.MessageID, timestamp, timestamp, timestamp) - fmt.Println("Delivered response!") - - case "PurchaseTitle": - fmt.Println("PT") - PT := PT{} - if err = xml.Unmarshal(body, &PT); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "if you wanna fun time, its gonna cost ya extra sweetie. ;3") - fmt.Printf("Error: %s", err.Error()) - return - } - fmt.Println(PT) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - - 2018 - POINTS - - - 00000000 - %s - PURCHGAME - - %s - 00000000 - 00000000 - 00000000 - 00000000 - - -`, PT.Version, PT.DeviceID, PT.MessageID, timestamp, timestamp, timestamp) - fmt.Println("Delivered response!") - - case "CheckRegistration": - fmt.Println("CR.") - CR := CR{} - if err = xml.Unmarshal(body, &CR); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "not good enough for me. ;3") - fmt.Printf("Error: %s", err.Error()) - return - } - fmt.Println(CR) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - %s - R - - -`, CR.Version, CR.DeviceID, CR.DeviceID, timestamp, CR.SerialNo) - fmt.Println("[i] Delivered response!") - - case "GetRegistrationInfo": - fmt.Println("GRI.") - GRI := GRI{} - if err = xml.Unmarshal(body, &GRI); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "how dirty. ;3") - fmt.Printf("Error: %s", err.Error()) - return - } - fmt.Println(GRI) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - %s - 00000000 - false - %s - - 0000000000000000 - R - POINTS - - -`, GRI.Version, GRI.DeviceID, GRI.MessageID, timestamp, GRI.AccountID, GRI.Country) - fmt.Println("Delivered response!") - - case "Register": - fmt.Println("REG.") - REG := REG{} - if err = xml.Unmarshal(body, ®); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "disgustingly invalid. ;3") - fmt.Printf("Error: %s", err.Error()) - return - } - fmt.Println(REG) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - %s - 00000000 - %s - - 00000000 - - -`, REG.Version, REG.DeviceID, REG.MessageID, timestamp, REG.AccountID, REG.Country) - fmt.Println("Delivered response!") - - case "Unregister": - fmt.Println("UNR.") - UNR := UNR{} - if err = xml.Unmarshal(body, &UNR); err != nil { - fmt.Println("...or not. Bad or incomplete request. (End processing.)") - fmt.Fprint(w, "how abnormal... ;3") - fmt.Printf("Error: %s", err.Error()) - return - } - fmt.Println(UNR) - fmt.Println("The request is valid! Responding...") - fmt.Fprintf(w, ` - - - - %s - %s - %s - %s - 0 - false - - -`, UNR.Version, UNR.DeviceID, UNR.MessageID, timestamp) - fmt.Println("Delivered response!") - - default: - fmt.Fprintf(w, "WiiSOAP can't handle this. Try again later or actually use a Wii instead of a computer.") - } - - // TODO: Add NUS and CAS SOAP to the case list. - fmt.Println("[!] End of Request." + "\n") -} - -func namespaceForType(service string) string { - return "urn:" + service + ".wsapi.broadon.com" -} - -// Expected contents are along the lines of "urn:ecs.wsapi.broadon.com/CheckDeviceStatus" -func parseAction(original string, service string) string { - prefix := namespaceForType(service) + "/" - stripped := strings.Replace(original, prefix, "", 1) - - if stripped == original { - // This doesn't appear valid. - return "" - } else { - return stripped - } + // From here on out, all special cool things should go into their respective handler function. } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..3adb2ca --- /dev/null +++ b/utils.go @@ -0,0 +1,37 @@ +// Copyright (C) 2018-2020 CornierKhan1 +// +// WiiSOAP is SOAP Server Software, designed specifically to handle Wii Shop Channel SOAP. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. + +package main + +import "strings" + +func namespaceForType(service string) string { + return "urn:" + service + ".wsapi.broadon.com" +} + +// Expected contents are along the lines of "urn:ecs.wsapi.broadon.com/CheckDeviceStatus" +func parseAction(original string, service string) string { + prefix := namespaceForType(service) + "/" + stripped := strings.Replace(original, prefix, "", 1) + + if stripped == original { + // This doesn't appear valid. + return "" + } else { + return stripped + } +}