source: code/trunk/upstream.go@ 34

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

Handle third-party JOIN and PART messages

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