blob: 101986164e321a9082efc2d2c1e20bd62925881c [file] [log] [blame]
Serge Bazanski1572e522020-12-03 23:19:28 +01001package main
2
3import (
4 "bytes"
5 "context"
6 "encoding/xml"
7 "fmt"
8 "io/ioutil"
9 "net/http"
10 "regexp"
11 "strings"
12
13 "github.com/golang/glog"
14)
15
16type Envelope struct {
17 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
18 Body *Body
19}
20
21type Body struct {
22 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
23 Request *Request
24 Response *Response
25 Fault *Fault
26}
27
28type Request struct {
29 XMLName xml.Name `xml:"urn:AC executeCommand"`
30 Command string `xml:"command"`
31}
32type Response struct {
33 XMLName xml.Name `xml:"urn:AC executeCommandResponse"`
34 Result string `xml:"result"`
35}
36
37type Fault struct {
38 XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
39 Code string `xml:"faultcode"`
40 String string `xml:"faultstring"`
41}
42
43type commandRes struct {
44 result string
45 fault string
46}
47
48var (
49 reAccount = regexp.MustCompile(`^[a-z0-9\-_\.]{3,16}$`)
50 rePassword = regexp.MustCompile(`^[a-zA-Z0-9\-_\.]{3,16}$`)
51)
52
53func createAccount(ctx context.Context, name, password string) error {
54 if !reAccount.MatchString(name) {
55 return fmt.Errorf("invalid account name")
56 }
57 if !rePassword.MatchString(password) {
58 return fmt.Errorf("invalid password name")
59 }
60 res, err := runCommand(ctx, fmt.Sprintf("account create %s %s", name, password))
61 if err != nil {
62 glog.Errorf("Account create: %v", err)
63 return fmt.Errorf("server unavailable")
64 }
65 if res.result == fmt.Sprintf("Account created: %s", name) {
66 glog.Infof("Created account %q", name)
67 return nil
68 }
69 glog.Errorf("Account create fault: %q/%q", res.fault, res.result)
70 return fmt.Errorf("server error")
71}
72
73func ensureAccount(ctx context.Context, name, password string) error {
74 if !reAccount.MatchString(name) {
75 return fmt.Errorf("invalid account name")
76 }
77 if !rePassword.MatchString(password) {
78 return fmt.Errorf("invalid password name")
79 }
80 res, err := runCommand(ctx, fmt.Sprintf("account create %s %s", name, password))
81 if err != nil {
82 glog.Errorf("Account create: %v", err)
83 return fmt.Errorf("server unavailable")
84 }
85 if res.result == fmt.Sprintf("Account created: %s", name) {
86 glog.Infof("Created account %q", name)
87 return nil
88 }
89 if res.fault != "Account with this name already exist!" {
90 glog.Errorf("Account create fault: %q/%q", res.fault, res.result)
91 return fmt.Errorf("server error")
92 }
93
94 res, err = runCommand(ctx, fmt.Sprintf("account set password %s %s %s", name, password, password))
95 if res.result == "The password was changed" {
96 glog.Infof("Updated password for account %q", name)
97 return nil
98 }
99 glog.Infof("password update fault: %q/%q", res.fault, res.result)
100 return fmt.Errorf("server error")
101}
102
103func runCommand(ctx context.Context, cmd string) (*commandRes, error) {
104 data, err := xml.Marshal(&Envelope{
105 Body: &Body{
106 Request: &Request{
107 Command: cmd,
108 },
109 },
110 })
111 if err != nil {
112 return nil, fmt.Errorf("marshal: %w", err)
113 }
114 buf := bytes.NewBuffer(data)
115 req, err := http.NewRequestWithContext(ctx, "POST", flagSOAPAddress, buf)
116 if err != nil {
117 return nil, fmt.Errorf("NewRequest(POST, %q): %w", flagSOAPAddress, err)
118 }
119
120 req.SetBasicAuth(flagSOAPUsername, flagSOAPPassword)
121 resp, err := http.DefaultClient.Do(req)
122 if err != nil {
123 return nil, fmt.Errorf("req.Do: %w", err)
124 }
125 defer resp.Body.Close()
126
127 respBytes, err := ioutil.ReadAll(resp.Body)
128 if err != nil {
129 return nil, fmt.Errorf("ReadAll response: %w", err)
130 }
131
132 respEnvelope := Envelope{}
133 err = xml.Unmarshal(respBytes, &respEnvelope)
134 if err != nil {
135 return nil, fmt.Errorf("unmarshal: %w", err)
136 }
137
138 if respEnvelope.Body == nil {
139 return nil, fmt.Errorf("no body returned")
140 }
141
142 if respEnvelope.Body.Fault != nil {
143 fault := respEnvelope.Body.Fault
144 if fault.Code == "SOAP-ENV:Client" {
145 return &commandRes{
146 fault: strings.TrimSpace(fault.String),
147 }, nil
148 }
149 return nil, fmt.Errorf("SOAP error %q: %v", fault.Code, fault.String)
150 }
151
152 result := ""
153 if respEnvelope.Body.Response != nil {
154 result = respEnvelope.Body.Response.Result
155 }
156
157 return &commandRes{
158 result: strings.TrimSpace(result),
159 }, nil
160}
161
162type playerinfo struct {
163 Account string
164 Character string
165}
166
167func onlinelist(ctx context.Context) ([]playerinfo, error) {
168 res, err := runCommand(ctx, "account onlinelist")
169 if err != nil {
170 glog.Errorf("onlinelist: %v", err)
171 return nil, fmt.Errorf("server unavailable")
172 }
173 if res.fault != "" {
174 glog.Errorf("onlinelist fault: %q", res.fault)
175 return nil, fmt.Errorf("server unavailable")
176 }
177
178 lines := strings.Split(res.result, "\n")
179 header := false
180 var pi []playerinfo
181 for _, line := range lines {
182 switch {
183 case strings.HasPrefix(line, "-="):
184 continue
185 case strings.HasPrefix(line, "-["):
186 default:
187 glog.Warningf("unparseable line %q", line)
188 continue
189 }
190 if !header {
191 header = true
192 continue
193 }
194 if len(line) != 69 {
195 glog.Warningf("wrong line length: %q", line)
196 continue
197 }
198 account := strings.ToLower(strings.TrimSpace(line[2:18]))
199 if line[18:20] != "][" {
200 glog.Warningf("unparseable line %q (wrong sep1)", line)
201 continue
202 }
203 character := strings.TrimSpace(line[20:32])
204 if line[32:34] != "][" {
205 glog.Warningf("unparseable line %q (wrong sep2)", line)
206 continue
207 }
208 pi = append(pi, playerinfo{
209 Account: account,
210 Character: character,
211 })
212 }
213 glog.Infof("Onlinelist: %v", pi)
214 return pi, nil
215}