source: code/trunk/upstream.go@ 35

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

Handle channel mode changes

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