source: code/trunk/upstream.go@ 19

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

Join channels on upstream servers

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