source: code/trunk/upstream.go@ 48

Last change on this file since 48 was 46, checked in by contact, 5 years ago

Handle downstream MODE messages

File size: 7.8 KB
Line 
1package jounce
2
3import (
4 "crypto/tls"
5 "fmt"
6 "io"
7 "net"
8 "strconv"
9 "strings"
10 "time"
11
12 "gopkg.in/irc.v3"
13)
14
15type upstreamChannel struct {
16 Name string
17 conn *upstreamConn
18 Topic string
19 TopicWho string
20 TopicTime time.Time
21 Status channelStatus
22 modes modeSet
23 Members map[string]membership
24 complete bool
25}
26
27type upstreamConn struct {
28 upstream *Upstream
29 logger Logger
30 net net.Conn
31 irc *irc.Conn
32 srv *Server
33 user *user
34 messages chan<- *irc.Message
35
36 serverName string
37 availableUserModes string
38 availableChannelModes string
39 channelModesWithParam string
40
41 registered bool
42 nick string
43 closed bool
44 modes modeSet
45 channels map[string]*upstreamChannel
46}
47
48func connectToUpstream(u *user, upstream *Upstream) (*upstreamConn, error) {
49 logger := &prefixLogger{u.srv.Logger, fmt.Sprintf("upstream %q: ", upstream.Addr)}
50 logger.Printf("connecting to server")
51
52 netConn, err := tls.Dial("tcp", upstream.Addr, nil)
53 if err != nil {
54 return nil, fmt.Errorf("failed to dial %q: %v", upstream.Addr, err)
55 }
56
57 msgs := make(chan *irc.Message, 64)
58 conn := &upstreamConn{
59 upstream: upstream,
60 logger: logger,
61 net: netConn,
62 irc: irc.NewConn(netConn),
63 srv: u.srv,
64 user: u,
65 messages: msgs,
66 channels: make(map[string]*upstreamChannel),
67 }
68
69 go func() {
70 for msg := range msgs {
71 if err := conn.irc.WriteMessage(msg); err != nil {
72 conn.logger.Printf("failed to write message: %v", err)
73 }
74 }
75 if err := conn.net.Close(); err != nil {
76 conn.logger.Printf("failed to close connection: %v", err)
77 } else {
78 conn.logger.Printf("connection closed")
79 }
80 }()
81
82 return conn, nil
83}
84
85func (c *upstreamConn) Close() error {
86 if c.closed {
87 return fmt.Errorf("upstream connection already closed")
88 }
89 close(c.messages)
90 c.closed = true
91 return nil
92}
93
94func (c *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
95 ch, ok := c.channels[name]
96 if !ok {
97 return nil, fmt.Errorf("unknown channel %q", name)
98 }
99 return ch, nil
100}
101
102func (c *upstreamConn) handleMessage(msg *irc.Message) error {
103 switch msg.Command {
104 case "PING":
105 // TODO: handle params
106 c.messages <- &irc.Message{
107 Command: "PONG",
108 Params: []string{c.srv.Hostname},
109 }
110 return nil
111 case "MODE":
112 var name, modeStr string
113 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
114 return err
115 }
116
117 if name == msg.Prefix.Name { // user mode change
118 if name != c.nick {
119 return fmt.Errorf("received MODE message for unknow nick %q", name)
120 }
121 return c.modes.Apply(modeStr)
122 } else { // channel mode change
123 ch, err := c.getChannel(name)
124 if err != nil {
125 return err
126 }
127 if err := ch.modes.Apply(modeStr); err != nil {
128 return err
129 }
130 }
131
132 c.user.forEachDownstream(func(dc *downstreamConn) {
133 dc.messages <- msg
134 })
135 case "NOTICE":
136 c.logger.Print(msg)
137 case irc.RPL_WELCOME:
138 c.registered = true
139 c.logger.Printf("connection registered")
140
141 for _, ch := range c.upstream.Channels {
142 c.messages <- &irc.Message{
143 Command: "JOIN",
144 Params: []string{ch},
145 }
146 }
147 case irc.RPL_MYINFO:
148 if err := parseMessageParams(msg, nil, &c.serverName, nil, &c.availableUserModes, &c.availableChannelModes); err != nil {
149 return err
150 }
151 if len(msg.Params) > 5 {
152 c.channelModesWithParam = msg.Params[5]
153 }
154 case "NICK":
155 var newNick string
156 if err := parseMessageParams(msg, &newNick); err != nil {
157 return err
158 }
159
160 if msg.Prefix.Name == c.nick {
161 c.logger.Printf("changed nick from %q to %q", c.nick, newNick)
162 c.nick = newNick
163 }
164
165 for _, ch := range c.channels {
166 if membership, ok := ch.Members[msg.Prefix.Name]; ok {
167 delete(ch.Members, msg.Prefix.Name)
168 ch.Members[newNick] = membership
169 }
170 }
171
172 c.user.forEachDownstream(func(dc *downstreamConn) {
173 dc.messages <- msg
174 })
175 case "JOIN":
176 var channels string
177 if err := parseMessageParams(msg, &channels); err != nil {
178 return err
179 }
180
181 for _, ch := range strings.Split(channels, ",") {
182 if msg.Prefix.Name == c.nick {
183 c.logger.Printf("joined channel %q", ch)
184 c.channels[ch] = &upstreamChannel{
185 Name: ch,
186 conn: c,
187 Members: make(map[string]membership),
188 }
189 } else {
190 ch, err := c.getChannel(ch)
191 if err != nil {
192 return err
193 }
194 ch.Members[msg.Prefix.Name] = 0
195 }
196 }
197
198 c.user.forEachDownstream(func(dc *downstreamConn) {
199 dc.messages <- msg
200 })
201 case "PART":
202 var channels string
203 if err := parseMessageParams(msg, &channels); err != nil {
204 return err
205 }
206
207 for _, ch := range strings.Split(channels, ",") {
208 if msg.Prefix.Name == c.nick {
209 c.logger.Printf("parted channel %q", ch)
210 delete(c.channels, ch)
211 } else {
212 ch, err := c.getChannel(ch)
213 if err != nil {
214 return err
215 }
216 delete(ch.Members, msg.Prefix.Name)
217 }
218 }
219
220 c.user.forEachDownstream(func(dc *downstreamConn) {
221 dc.messages <- msg
222 })
223 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
224 var name, topic string
225 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
226 return err
227 }
228 ch, err := c.getChannel(name)
229 if err != nil {
230 return err
231 }
232 if msg.Command == irc.RPL_TOPIC {
233 ch.Topic = topic
234 } else {
235 ch.Topic = ""
236 }
237 case "TOPIC":
238 var name string
239 if err := parseMessageParams(msg, nil, &name); err != nil {
240 return err
241 }
242 ch, err := c.getChannel(name)
243 if err != nil {
244 return err
245 }
246 if len(msg.Params) > 1 {
247 ch.Topic = msg.Params[1]
248 } else {
249 ch.Topic = ""
250 }
251 case rpl_topicwhotime:
252 var name, who, timeStr string
253 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
254 return err
255 }
256 ch, err := c.getChannel(name)
257 if err != nil {
258 return err
259 }
260 ch.TopicWho = who
261 sec, err := strconv.ParseInt(timeStr, 10, 64)
262 if err != nil {
263 return fmt.Errorf("failed to parse topic time: %v", err)
264 }
265 ch.TopicTime = time.Unix(sec, 0)
266 case irc.RPL_NAMREPLY:
267 var name, statusStr, members string
268 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
269 return err
270 }
271 ch, err := c.getChannel(name)
272 if err != nil {
273 return err
274 }
275
276 status, err := parseChannelStatus(statusStr)
277 if err != nil {
278 return err
279 }
280 ch.Status = status
281
282 for _, s := range strings.Split(members, " ") {
283 membership, nick := parseMembershipPrefix(s)
284 ch.Members[nick] = membership
285 }
286 case irc.RPL_ENDOFNAMES:
287 var name string
288 if err := parseMessageParams(msg, nil, &name); err != nil {
289 return err
290 }
291 ch, err := c.getChannel(name)
292 if err != nil {
293 return err
294 }
295
296 if ch.complete {
297 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
298 }
299 ch.complete = true
300
301 c.user.forEachDownstream(func(dc *downstreamConn) {
302 forwardChannel(dc, ch)
303 })
304 case "PRIVMSG":
305 c.user.forEachDownstream(func(dc *downstreamConn) {
306 dc.messages <- msg
307 })
308 case irc.RPL_YOURHOST, irc.RPL_CREATED:
309 // Ignore
310 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
311 // Ignore
312 case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
313 // Ignore
314 case rpl_localusers, rpl_globalusers:
315 // Ignore
316 case irc.RPL_STATSVLINE, irc.RPL_STATSPING, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
317 // Ignore
318 default:
319 c.logger.Printf("unhandled upstream message: %v", msg)
320 }
321 return nil
322}
323
324func (c *upstreamConn) register() {
325 c.nick = c.upstream.Nick
326 c.messages <- &irc.Message{
327 Command: "NICK",
328 Params: []string{c.upstream.Nick},
329 }
330 c.messages <- &irc.Message{
331 Command: "USER",
332 Params: []string{c.upstream.Username, "0", "*", c.upstream.Realname},
333 }
334}
335
336func (c *upstreamConn) readMessages() error {
337 for {
338 msg, err := c.irc.ReadMessage()
339 if err == io.EOF {
340 break
341 } else if err != nil {
342 return fmt.Errorf("failed to read IRC command: %v", err)
343 }
344
345 if err := c.handleMessage(msg); err != nil {
346 c.logger.Printf("failed to handle message %q: %v", msg, err)
347 }
348 }
349
350 return nil
351}
Note: See TracBrowser for help on using the repository browser.