Serge Bazanski | ce2737f | 2021-05-23 17:15:29 +0200 | [diff] [blame] | 1 | package ident |
| 2 | |
| 3 | import ( |
| 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. |
| 15 | func 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. |
| 25 | type 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. |
| 44 | type 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. |
| 71 | type HandlerFunc func(ctx context.Context, w ResponseWriter, r *Request) |
| 72 | |
| 73 | // responseWriter implements ResponseWriter for a Server. |
| 74 | type 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. |
| 82 | func (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 | |
| 99 | func (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 | |
| 110 | func (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 | |
| 126 | var ( |
| 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). |
| 133 | func 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. |
| 143 | func (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. |
| 154 | func (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 | |
| 220 | func (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 | |
| 230 | func (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 | } |