package main import ( "bytes" "context" "encoding/xml" "fmt" "io/ioutil" "net/http" "regexp" "strings" "github.com/golang/glog" ) type Envelope struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` Body *Body } type Body struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` Request *Request Response *Response Fault *Fault } type Request struct { XMLName xml.Name `xml:"urn:AC executeCommand"` Command string `xml:"command"` } type Response struct { XMLName xml.Name `xml:"urn:AC executeCommandResponse"` Result string `xml:"result"` } type Fault struct { XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"` Code string `xml:"faultcode"` String string `xml:"faultstring"` } type commandRes struct { result string fault string } var ( reAccount = regexp.MustCompile(`^[a-z0-9\-_\.]{3,16}$`) rePassword = regexp.MustCompile(`^[a-zA-Z0-9\-_\.]{3,16}$`) ) func createAccount(ctx context.Context, name, password string) error { if !reAccount.MatchString(name) { return fmt.Errorf("invalid account name") } if !rePassword.MatchString(password) { return fmt.Errorf("invalid password name") } res, err := runCommand(ctx, fmt.Sprintf("account create %s %s", name, password)) if err != nil { glog.Errorf("Account create: %v", err) return fmt.Errorf("server unavailable") } if res.result == fmt.Sprintf("Account created: %s", name) { glog.Infof("Created account %q", name) return nil } glog.Errorf("Account create fault: %q/%q", res.fault, res.result) return fmt.Errorf("server error") } func ensureAccount(ctx context.Context, name, password string) error { if !reAccount.MatchString(name) { return fmt.Errorf("invalid account name") } if !rePassword.MatchString(password) { return fmt.Errorf("invalid password name") } res, err := runCommand(ctx, fmt.Sprintf("account create %s %s", name, password)) if err != nil { glog.Errorf("Account create: %v", err) return fmt.Errorf("server unavailable") } if res.result == fmt.Sprintf("Account created: %s", name) { glog.Infof("Created account %q", name) return nil } if res.fault != "Account with this name already exist!" { glog.Errorf("Account create fault: %q/%q", res.fault, res.result) return fmt.Errorf("server error") } res, err = runCommand(ctx, fmt.Sprintf("account set password %s %s %s", name, password, password)) if res.result == "The password was changed" { glog.Infof("Updated password for account %q", name) return nil } glog.Infof("password update fault: %q/%q", res.fault, res.result) return fmt.Errorf("server error") } func runCommand(ctx context.Context, cmd string) (*commandRes, error) { data, err := xml.Marshal(&Envelope{ Body: &Body{ Request: &Request{ Command: cmd, }, }, }) if err != nil { return nil, fmt.Errorf("marshal: %w", err) } buf := bytes.NewBuffer(data) req, err := http.NewRequestWithContext(ctx, "POST", flagSOAPAddress, buf) if err != nil { return nil, fmt.Errorf("NewRequest(POST, %q): %w", flagSOAPAddress, err) } req.SetBasicAuth(flagSOAPUsername, flagSOAPPassword) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("req.Do: %w", err) } defer resp.Body.Close() respBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("ReadAll response: %w", err) } respEnvelope := Envelope{} err = xml.Unmarshal(respBytes, &respEnvelope) if err != nil { return nil, fmt.Errorf("unmarshal: %w", err) } if respEnvelope.Body == nil { return nil, fmt.Errorf("no body returned") } if respEnvelope.Body.Fault != nil { fault := respEnvelope.Body.Fault if fault.Code == "SOAP-ENV:Client" { return &commandRes{ fault: strings.TrimSpace(fault.String), }, nil } return nil, fmt.Errorf("SOAP error %q: %v", fault.Code, fault.String) } result := "" if respEnvelope.Body.Response != nil { result = respEnvelope.Body.Response.Result } return &commandRes{ result: strings.TrimSpace(result), }, nil } type playerinfo struct { Account string Character string } func onlinelist(ctx context.Context) ([]playerinfo, error) { res, err := runCommand(ctx, "account onlinelist") if err != nil { glog.Errorf("onlinelist: %v", err) return nil, fmt.Errorf("server unavailable") } if res.fault != "" { glog.Errorf("onlinelist fault: %q", res.fault) return nil, fmt.Errorf("server unavailable") } lines := strings.Split(res.result, "\n") header := false var pi []playerinfo for _, line := range lines { switch { case strings.HasPrefix(line, "-="): continue case strings.HasPrefix(line, "-["): default: glog.Warningf("unparseable line %q", line) continue } if !header { header = true continue } if len(line) != 69 { glog.Warningf("wrong line length: %q", line) continue } account := strings.ToLower(strings.TrimSpace(line[2:18])) if line[18:20] != "][" { glog.Warningf("unparseable line %q (wrong sep1)", line) continue } character := strings.TrimSpace(line[20:32]) if line[32:34] != "][" { glog.Warningf("unparseable line %q (wrong sep2)", line) continue } pi = append(pi, playerinfo{ Account: account, Character: character, }) } glog.Infof("Onlinelist: %v", pi) return pi, nil }