source: code/trunk/upstream.go@ 22

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

Add per-upstream logger

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