source: code/trunk/service.go@ 254

Last change on this file since 254 was 252, checked in by admin, 5 years ago

Allow users to change password in client

Added a BouncerServ command for that.

File size: 6.3 KB
Line 
1package soju
2
3import (
4 "flag"
5 "fmt"
6 "io/ioutil"
7 "strings"
8
9 "github.com/google/shlex"
10 "golang.org/x/crypto/bcrypt"
11 "gopkg.in/irc.v3"
12)
13
14const serviceNick = "BouncerServ"
15
16var servicePrefix = &irc.Prefix{
17 Name: serviceNick,
18 User: serviceNick,
19 Host: serviceNick,
20}
21
22type serviceCommandSet map[string]*serviceCommand
23
24type serviceCommand struct {
25 usage string
26 desc string
27 handle func(dc *downstreamConn, params []string) error
28 children serviceCommandSet
29}
30
31func sendServiceNOTICE(dc *downstreamConn, text string) {
32 dc.SendMessage(&irc.Message{
33 Prefix: servicePrefix,
34 Command: "NOTICE",
35 Params: []string{dc.nick, text},
36 })
37}
38
39func sendServicePRIVMSG(dc *downstreamConn, text string) {
40 dc.SendMessage(&irc.Message{
41 Prefix: servicePrefix,
42 Command: "PRIVMSG",
43 Params: []string{dc.nick, text},
44 })
45}
46
47func handleServicePRIVMSG(dc *downstreamConn, text string) {
48 words, err := shlex.Split(text)
49 if err != nil {
50 sendServicePRIVMSG(dc, fmt.Sprintf("error: failed to parse command: %v", err))
51 return
52 }
53
54 cmd, params, err := serviceCommands.Get(words)
55 if err != nil {
56 sendServicePRIVMSG(dc, fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
57 return
58 }
59
60 if err := cmd.handle(dc, params); err != nil {
61 sendServicePRIVMSG(dc, fmt.Sprintf("error: %v", err))
62 }
63}
64
65func (cmds serviceCommandSet) Get(params []string) (*serviceCommand, []string, error) {
66 if len(params) == 0 {
67 return nil, nil, fmt.Errorf("no command specified")
68 }
69
70 name := params[0]
71 params = params[1:]
72
73 cmd, ok := cmds[name]
74 if !ok {
75 for k := range cmds {
76 if !strings.HasPrefix(k, name) {
77 continue
78 }
79 if cmd != nil {
80 return nil, params, fmt.Errorf("command %q is ambiguous", name)
81 }
82 cmd = cmds[k]
83 }
84 }
85 if cmd == nil {
86 return nil, params, fmt.Errorf("command %q not found", name)
87 }
88
89 if len(params) == 0 || len(cmd.children) == 0 {
90 return cmd, params, nil
91 }
92 return cmd.children.Get(params)
93}
94
95var serviceCommands serviceCommandSet
96
97func init() {
98 serviceCommands = serviceCommandSet{
99 "help": {
100 usage: "[command]",
101 desc: "print help message",
102 handle: handleServiceHelp,
103 },
104 "network": {
105 children: serviceCommandSet{
106 "create": {
107 usage: "-addr <addr> [-name name] [-username username] [-pass pass] [-realname realname] [-nick nick]",
108 desc: "add a new network",
109 handle: handleServiceCreateNetwork,
110 },
111 "status": {
112 desc: "show a list of saved networks and their current status",
113 handle: handleServiceNetworkStatus,
114 },
115 "delete": {
116 usage: "<name>",
117 desc: "delete a network",
118 handle: handleServiceNetworkDelete,
119 },
120 },
121 },
122 "change-password": {
123 usage: "<new password>",
124 desc: "change your password",
125 handle: handlePasswordChange,
126 },
127 }
128}
129
130func appendServiceCommandSetHelp(cmds serviceCommandSet, prefix []string, l *[]string) {
131 for name, cmd := range cmds {
132 words := append(prefix, name)
133 if len(cmd.children) == 0 {
134 s := strings.Join(words, " ")
135 *l = append(*l, s)
136 } else {
137 appendServiceCommandSetHelp(cmd.children, words, l)
138 }
139 }
140}
141
142func handleServiceHelp(dc *downstreamConn, params []string) error {
143 if len(params) > 0 {
144 cmd, rest, err := serviceCommands.Get(params)
145 if err != nil {
146 return err
147 }
148 words := params[:len(params)-len(rest)]
149
150 if len(cmd.children) > 0 {
151 var l []string
152 appendServiceCommandSetHelp(cmd.children, words, &l)
153 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
154 } else {
155 text := strings.Join(words, " ")
156 if cmd.usage != "" {
157 text += " " + cmd.usage
158 }
159 text += ": " + cmd.desc
160
161 sendServicePRIVMSG(dc, text)
162 }
163 } else {
164 var l []string
165 appendServiceCommandSetHelp(serviceCommands, nil, &l)
166 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
167 }
168 return nil
169}
170
171func newFlagSet() *flag.FlagSet {
172 fs := flag.NewFlagSet("", flag.ContinueOnError)
173 fs.SetOutput(ioutil.Discard)
174 return fs
175}
176
177func handleServiceCreateNetwork(dc *downstreamConn, params []string) error {
178 fs := newFlagSet()
179 addr := fs.String("addr", "", "")
180 name := fs.String("name", "", "")
181 username := fs.String("username", "", "")
182 pass := fs.String("pass", "", "")
183 realname := fs.String("realname", "", "")
184 nick := fs.String("nick", "", "")
185
186 if err := fs.Parse(params); err != nil {
187 return err
188 }
189 if *addr == "" {
190 return fmt.Errorf("flag -addr is required")
191 }
192
193 if *nick == "" {
194 *nick = dc.nick
195 }
196
197 var err error
198 network, err := dc.user.createNetwork(&Network{
199 Addr: *addr,
200 Name: *name,
201 Username: *username,
202 Pass: *pass,
203 Realname: *realname,
204 Nick: *nick,
205 })
206 if err != nil {
207 return fmt.Errorf("could not create network: %v", err)
208 }
209
210 sendServicePRIVMSG(dc, fmt.Sprintf("created network %q", network.GetName()))
211 return nil
212}
213
214func handleServiceNetworkStatus(dc *downstreamConn, params []string) error {
215 dc.user.forEachNetwork(func(net *network) {
216 var statuses []string
217 var details string
218 if uc := net.upstream(); uc != nil {
219 statuses = append(statuses, "connected as "+uc.nick)
220 details = fmt.Sprintf("%v channels", len(uc.channels))
221 } else {
222 statuses = append(statuses, "disconnected")
223 if net.lastError != nil {
224 details = net.lastError.Error()
225 }
226 }
227
228 if net == dc.network {
229 statuses = append(statuses, "current")
230 }
231
232 name := net.GetName()
233 if name != net.Addr {
234 name = fmt.Sprintf("%v (%v)", name, net.Addr)
235 }
236
237 s := fmt.Sprintf("%v [%v]", name, strings.Join(statuses, ", "))
238 if details != "" {
239 s += ": " + details
240 }
241 sendServicePRIVMSG(dc, s)
242 })
243 return nil
244}
245
246func handleServiceNetworkDelete(dc *downstreamConn, params []string) error {
247 if len(params) != 1 {
248 return fmt.Errorf("expected exactly one argument")
249 }
250
251 net := dc.user.getNetwork(params[0])
252 if net == nil {
253 return fmt.Errorf("unknown network %q", params[0])
254 }
255
256 if err := dc.user.deleteNetwork(net.ID); err != nil {
257 return err
258 }
259
260 sendServicePRIVMSG(dc, fmt.Sprintf("deleted network %q", net.GetName()))
261 return nil
262}
263
264func handlePasswordChange(dc *downstreamConn, params []string) error {
265 if len(params) != 1 {
266 return fmt.Errorf("expected exactly one argument")
267 }
268
269 hashed, err := bcrypt.GenerateFromPassword([]byte(params[0]), bcrypt.DefaultCost)
270 if err != nil {
271 return fmt.Errorf("failed to hash password: %v", err)
272 }
273 if err := dc.user.updatePassword(string(hashed)); err != nil {
274 return err
275 }
276
277 sendServicePRIVMSG(dc, "password updated")
278 return nil
279}
Note: See TracBrowser for help on using the repository browser.