blob: ac985d9bb6109812db195ec63ddd4c17cb295068 [file] [log] [blame]
Serge Bazanskice2737f2021-05-23 17:15:29 +02001package ident
2
3import (
4 "bufio"
5 "context"
6 "fmt"
7 "net"
8 "sync"
9 "time"
10
11 "github.com/golang/glog"
12)
13
14// NewServer returns an ident Server.
15func NewServer() *Server {
16 return &Server{
17 handler: unsetHandler,
18 }
19}
20
21// Server is an ident protocol server (per RFC1413). It must be configured with
22// a HandlerFunc before Serve is called.
23// Multiple goroutines may invoke methods on Server simultaneously, but the
24// Server can only Serve one listener at a time.
25type Server struct {
26 handler HandlerFunc
27 // mu guards stopC and stop.
28 mu sync.Mutex
29 // stopC is set if Serve() is already running. If it gets closed, Serve()
30 // will quit and set stopC to nil.
31 stopC chan struct{}
32 // stop can be set to true if Serve() is not yet running but has already
33 // been requested to Stop() (eg. if Serve() is ran in a goroutine which
34 // hasn't yet scheduled). It will be set back to false when Serve() sees it
35 // set and exits.
36 stop bool
37}
38
39// ResponseWriter is passed to HandlerFuncs and is used to signal to the Server
40// that the HandlerFunc wants to respond to the incoming Request in a certain
41// way.
42// Only the goroutine that the HandlerFunc has been started in may invoke
43// methods on the ResponseWriter.
44type ResponseWriter interface {
45 // SendError returns an ident ErrorResponse to the ident client. This can
46 // only be called once, and cannot be called after SendIdent.
47 SendError(ErrorResponse) error
48 // SendIdent returns an ident IdentResponse to the ident client. This can
49 // only be called once, and cannot be called after SendError.
50 SendIdent(*IdentResponse) error
51}
52
53// HandlerFunc is a function that will be called to serve a given ident
54// Request. Users of the Server must implement this and configure a Server to
55// use it by invoking Server.HandleFunc.
56// Each HandlerFunc will be started in its own goroutine. When HandlerFunc
57// returns, the Server will attempt to serve more incoming requests from the
58// ident client.
59// The Server does not limit the amount of concurrent ident connections that it
60// serves. If the Server user wishes to limit concurrency, she must do it
61// herself, eg. by using a semaphore. The Server will continue accepting new
62// connections and starting new HandlerFuncs, if the user code needs to push
63// back it should return as early as possible. There currently is no way to
64// make the Server refuse connections above some concurrncy limit.
65// The Server does not impose any execution timeout on handlers. If the Server
66// user wishes to impose an execution timeout, she must do it herself, eg.
67// using context.WithTimeout or time.After.
68// The passed Context will be canceled when the ident client disconnects or the
69// Server shuts down. The HandlerFunc must return as early as it can detect
70// that the context is done.
71type HandlerFunc func(ctx context.Context, w ResponseWriter, r *Request)
72
73// responseWriter implements ResponseWriter for a Server.
74type responseWriter struct {
75 conn net.Conn
76 req *Request
77 responded bool
78}
79
80// sendResponse sends a Response to the ident client. The Response must already
81// be fully populated.
82func (w *responseWriter) sendResponse(r *Response) error {
83 if w.responded {
84 return fmt.Errorf("handler already sent a response")
85 }
86 w.responded = true
87 data := r.encode()
88 if data == nil {
89 return fmt.Errorf("failed to encode response")
90 }
91 glog.V(3).Infof(" -> %q", data)
92 _, err := w.conn.Write(data)
93 if err != nil {
94 return fmt.Errorf("writing response failed: %w", err)
95 }
96 return nil
97}
98
99func (w *responseWriter) SendError(e ErrorResponse) error {
100 if !e.IsError() {
101 return fmt.Errorf("error response must contain a valid error")
102 }
103 return w.sendResponse(&Response{
104 ClientPort: w.req.ClientPort,
105 ServerPort: w.req.ServerPort,
106 Error: e,
107 })
108}
109
110func (w *responseWriter) SendIdent(i *IdentResponse) error {
111 ir := *i
112 // TODO(q3k): enforce RFC1413 limits.
113 if ir.OperatingSystem == "" {
114 ir.OperatingSystem = "UNIX"
115 }
116 if ir.UserID == "" {
117 return fmt.Errorf("ident response must have UserID set")
118 }
119 return w.sendResponse(&Response{
120 ClientPort: w.req.ClientPort,
121 ServerPort: w.req.ServerPort,
122 Ident: &ir,
123 })
124}
125
126var (
127 unsetHandlerErrorOnce sync.Once
128)
129
130// unsetHandler is the default handler that is configured for a Server. It
131// returns UNKNOWN-ERROR to the ident client and logs an error once if it's
132// called (telling the user about a misconfiguration / programming error).
133func unsetHandler(ctx context.Context, w ResponseWriter, r *Request) {
134 unsetHandlerErrorOnce.Do(func() {
135 glog.Errorf("Server with no handler configured - will always return UNKNOWN-ERROR")
136 })
137 w.SendError(UnknownError)
138}
139
140// HandleFunc sets the HandlerFunc that the server will call for every incoming
141// ident request. If a HandlerFunc is already set, it will be overwritten by
142// the given new function.
143func (s *Server) HandleFunc(fn HandlerFunc) {
144 s.handler = fn
145}
146
147// Serve runs the ident server, blocking until a transport-level error occurs
148// or Stop() is invoked. The returned error will be nil on Stop(), and will
149// wrap the underlying transport-level error otherwise.
150//
151// Only one invokation of Serve() can be run at a time, but Serve can be called
152// again after Stop() is called, and can be ran on a different Listener - no
153// state is kept in between subsequent Serve() runs.
154func (s *Server) Serve(lis net.Listener) error {
155 s.mu.Lock()
156 if s.stopC != nil {
157 s.mu.Unlock()
158 return fmt.Errorf("cannot Serve() an already serving server")
159 }
160 // Stop() has been invoked before Serve() started.
161 if s.stop == true {
162 s.stop = false
163 s.mu.Unlock()
164 return nil
165 }
166 // Set stopC to allow Stop() calls to stop this running Serve(). It will be
167 // set to nil on exit.
168 stopC := make(chan struct{})
169 s.stopC = stopC
170 s.mu.Unlock()
171
172 defer func() {
173 s.mu.Lock()
174 s.stopC = nil
175 s.mu.Unlock()
176 }()
177
178 ctx, ctxC := context.WithCancel(context.Background())
179 for {
180 lisConnC := make(chan net.Conn)
181 lisErrC := make(chan error)
182 go func() {
183 conn, err := lis.Accept()
184 select {
185 case <-stopC:
186 // Server stopped, drop the accepted connection (if any)
187 // and return.
188 glog.V(2).Infof("Accept goroutine stopping...")
189 if err == nil {
190 conn.Close()
191 }
192 return
193 default:
194 }
195 if err == nil {
196 glog.V(5).Infof("Accept ok: %v", conn.RemoteAddr())
197 lisConnC <- conn
198 } else {
199 glog.V(5).Infof("Accept err: %v", err)
200 lisErrC <- err
201 }
202 }()
203
204 select {
205 case <-stopC:
206 // Server stopped, return.
207 ctxC()
208 return nil
209 case err := <-lisErrC:
210 ctxC()
211 // Accept() failed, return error.
212 return err
213 case conn := <-lisConnC:
214 // Accept() succeeded, serve request.
215 go s.serve(ctx, conn)
216 }
217 }
218}
219
220func (s *Server) Stop() {
221 s.mu.Lock()
222 defer s.mu.Unlock()
223 if s.stopC != nil {
224 close(s.stopC)
225 } else {
226 s.stop = true
227 }
228}
229
230func (s *Server) serve(ctx context.Context, conn net.Conn) {
231 glog.V(2).Infof("Serving connection %v", conn.RemoteAddr())
232 scanner := bufio.NewScanner(conn)
233 // The RFC does not place a limit on the request line length, only on
234 // response length. We set an arbitrary limit to 1024 bytes.
235 scanner.Buffer(nil, 1024)
236
237 for {
238 // Implement an arbitrary timeout for receiving data from client.
239 // TODO(q3k): make this configurable
240 go func() {
241 timer := time.NewTimer(10 * time.Second)
242 defer timer.Stop()
243 select {
244 case <-ctx.Done():
245 return
246 case <-timer.C:
247 glog.V(1).Infof("Connection %v: terminating on receive timeout", conn.RemoteAddr())
248 conn.Close()
249 }
250 }()
251 if !scanner.Scan() {
252 err := scanner.Err()
253 if err == nil {
254 // EOF, just return.
255 return
256 }
257 // Some other transport level error occured, or the request line
258 // was too long. We can only log this and be done.
259 glog.V(1).Infof("Connection %v: scan failed: %v", conn.RemoteAddr(), err)
260 conn.Close()
261 return
262 }
263 data := scanner.Bytes()
264 glog.V(3).Infof(" <- %q", data)
265 req, err := decodeRequest(data)
266 if err != nil {
267 glog.V(1).Infof("Connection %v: could not decode request: %v", conn.RemoteAddr(), err)
268 conn.Close()
269 return
270 }
271 req.ClientAddress = conn.RemoteAddr()
272 rw := responseWriter{
273 req: req,
274 conn: conn,
275 }
276 s.handler(ctx, &rw, req)
277 if !rw.responded {
278 glog.Warningf("Connection %v: handler did not send response, sending UNKNOWN-ERROR", conn.RemoteAddr())
279 rw.SendError(UnknownError)
280 }
281 }
282}