source: code/trunk/upstream.go@ 29

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

Forward channel information

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