source: code/trunk/upstream.go@ 44

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

Add upstreamConn.register

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