source: code/trunk/upstream.go@ 37

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

Per-user connections

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