1package server 2 3import ( 4 "crypto/tls" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "runtime/debug" 10 "time" 11 12 "github.com/emersion/go-imap" 13 "github.com/emersion/go-imap/backend" 14) 15 16// Conn is a connection to a client. 17type Conn interface { 18 io.Reader 19 20 // Server returns this connection's server. 21 Server() *Server 22 // Context returns this connection's context. 23 Context() *Context 24 // Capabilities returns a list of capabilities enabled for this connection. 25 Capabilities() []string 26 // WriteResp writes a response to this connection. 27 WriteResp(res imap.WriterTo) error 28 // IsTLS returns true if TLS is enabled. 29 IsTLS() bool 30 // TLSState returns the TLS connection state if TLS is enabled, nil otherwise. 31 TLSState() *tls.ConnectionState 32 // Upgrade upgrades a connection, e.g. wrap an unencrypted connection with an 33 // encrypted tunnel. 34 Upgrade(upgrader imap.ConnUpgrader) error 35 // Close closes this connection. 36 Close() error 37 WaitReady() 38 39 Info() *imap.ConnInfo 40 41 setTLSConn(*tls.Conn) 42 silent() *bool // TODO: remove this 43 serve(Conn) error 44 commandHandler(cmd *imap.Command) (hdlr Handler, err error) 45} 46 47// Context stores a connection's metadata. 48type Context struct { 49 // This connection's current state. 50 State imap.ConnState 51 // If the client is logged in, the user. 52 User backend.User 53 // If the client has selected a mailbox, the mailbox. 54 Mailbox backend.Mailbox 55 // True if the currently selected mailbox has been opened in read-only mode. 56 MailboxReadOnly bool 57 // Responses to send to the client. 58 Responses chan<- imap.WriterTo 59 // Closed when the client is logged out. 60 LoggedOut <-chan struct{} 61} 62 63type conn struct { 64 *imap.Conn 65 66 conn Conn // With extensions overrides 67 s *Server 68 ctx *Context 69 tlsConn *tls.Conn 70 continues chan bool 71 upgrade chan bool 72 responses chan imap.WriterTo 73 loggedOut chan struct{} 74 silentVal bool 75} 76 77func newConn(s *Server, c net.Conn) *conn { 78 // Create an imap.Reader and an imap.Writer 79 continues := make(chan bool) 80 r := imap.NewServerReader(nil, continues) 81 w := imap.NewWriter(nil) 82 83 responses := make(chan imap.WriterTo) 84 loggedOut := make(chan struct{}) 85 86 tlsConn, _ := c.(*tls.Conn) 87 88 conn := &conn{ 89 Conn: imap.NewConn(c, r, w), 90 91 s: s, 92 ctx: &Context{ 93 State: imap.ConnectingState, 94 Responses: responses, 95 LoggedOut: loggedOut, 96 }, 97 tlsConn: tlsConn, 98 continues: continues, 99 upgrade: make(chan bool), 100 responses: responses, 101 loggedOut: loggedOut, 102 } 103 104 if s.Debug != nil { 105 conn.Conn.SetDebug(s.Debug) 106 } 107 if s.MaxLiteralSize > 0 { 108 conn.Conn.MaxLiteralSize = s.MaxLiteralSize 109 } 110 111 go conn.send() 112 113 return conn 114} 115 116func (c *conn) Server() *Server { 117 return c.s 118} 119 120func (c *conn) Context() *Context { 121 return c.ctx 122} 123 124type response struct { 125 response imap.WriterTo 126 done chan struct{} 127} 128 129func (r *response) WriteTo(w *imap.Writer) error { 130 err := r.response.WriteTo(w) 131 close(r.done) 132 return err 133} 134 135func (c *conn) setDeadline() { 136 if c.s.AutoLogout == 0 { 137 return 138 } 139 140 dur := c.s.AutoLogout 141 if dur < MinAutoLogout { 142 dur = MinAutoLogout 143 } 144 t := time.Now().Add(dur) 145 146 c.Conn.SetDeadline(t) 147} 148 149func (c *conn) WriteResp(r imap.WriterTo) error { 150 done := make(chan struct{}) 151 c.responses <- &response{r, done} 152 <-done 153 c.setDeadline() 154 return nil 155} 156 157func (c *conn) Close() error { 158 if c.ctx.User != nil { 159 c.ctx.User.Logout() 160 } 161 162 return c.Conn.Close() 163} 164 165func (c *conn) Capabilities() []string { 166 caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR"} 167 168 if c.ctx.State == imap.NotAuthenticatedState { 169 if !c.IsTLS() && c.s.TLSConfig != nil { 170 caps = append(caps, "STARTTLS") 171 } 172 173 if !c.canAuth() { 174 caps = append(caps, "LOGINDISABLED") 175 } else { 176 for name := range c.s.auths { 177 caps = append(caps, "AUTH="+name) 178 } 179 } 180 } 181 182 for _, ext := range c.s.extensions { 183 caps = append(caps, ext.Capabilities(c)...) 184 } 185 186 return caps 187} 188 189func (c *conn) writeAndFlush(w imap.WriterTo) error { 190 if err := w.WriteTo(c.Writer); err != nil { 191 return err 192 } 193 return c.Writer.Flush() 194} 195 196func (c *conn) send() { 197 // Send responses 198 for { 199 select { 200 case <-c.upgrade: 201 // Wait until upgrade is finished. 202 c.Wait() 203 case needCont := <-c.continues: 204 // Send continuation requests 205 if needCont { 206 resp := &imap.ContinuationReq{Info: "send literal"} 207 if err := c.writeAndFlush(resp); err != nil { 208 c.Server().ErrorLog.Println("cannot send continuation request: ", err) 209 } 210 } 211 case res := <-c.responses: 212 // Got a response that needs to be sent 213 // Request to send the response 214 if err := c.writeAndFlush(res); err != nil { 215 c.Server().ErrorLog.Println("cannot send response: ", err) 216 } 217 case <-c.loggedOut: 218 return 219 } 220 } 221} 222 223func (c *conn) greet() error { 224 c.ctx.State = imap.NotAuthenticatedState 225 226 caps := c.Capabilities() 227 args := make([]interface{}, len(caps)) 228 for i, cap := range caps { 229 args[i] = imap.RawString(cap) 230 } 231 232 greeting := &imap.StatusResp{ 233 Type: imap.StatusRespOk, 234 Code: imap.CodeCapability, 235 Arguments: args, 236 Info: "IMAP4rev1 Service Ready", 237 } 238 239 return c.WriteResp(greeting) 240} 241 242func (c *conn) setTLSConn(tlsConn *tls.Conn) { 243 c.tlsConn = tlsConn 244} 245 246func (c *conn) IsTLS() bool { 247 return c.tlsConn != nil 248} 249 250func (c *conn) TLSState() *tls.ConnectionState { 251 if c.tlsConn != nil { 252 state := c.tlsConn.ConnectionState() 253 return &state 254 } 255 return nil 256} 257 258// canAuth checks if the client can use plain text authentication. 259func (c *conn) canAuth() bool { 260 return c.IsTLS() || c.s.AllowInsecureAuth 261} 262 263func (c *conn) silent() *bool { 264 return &c.silentVal 265} 266 267func (c *conn) serve(conn Conn) (err error) { 268 c.conn = conn 269 270 defer func() { 271 c.ctx.State = imap.LogoutState 272 close(c.loggedOut) 273 }() 274 275 defer func() { 276 if r := recover(); r != nil { 277 c.WriteResp(&imap.StatusResp{ 278 Type: imap.StatusRespBye, 279 Info: "Internal server error, closing connection.", 280 }) 281 282 stack := debug.Stack() 283 c.s.ErrorLog.Printf("panic serving %v: %v\n%s", c.Info().RemoteAddr, r, stack) 284 285 err = fmt.Errorf("%v", r) 286 } 287 }() 288 289 // Send greeting 290 if err := c.greet(); err != nil { 291 return err 292 } 293 294 for { 295 if c.ctx.State == imap.LogoutState { 296 return nil 297 } 298 299 var res *imap.StatusResp 300 var up Upgrader 301 302 fields, err := c.ReadLine() 303 if err == io.EOF || c.ctx.State == imap.LogoutState { 304 return nil 305 } 306 c.setDeadline() 307 308 if err != nil { 309 if imap.IsParseError(err) { 310 res = &imap.StatusResp{ 311 Type: imap.StatusRespBad, 312 Info: err.Error(), 313 } 314 } else { 315 c.s.ErrorLog.Println("cannot read command:", err) 316 return err 317 } 318 } else { 319 cmd := &imap.Command{} 320 if err := cmd.Parse(fields); err != nil { 321 res = &imap.StatusResp{ 322 Tag: cmd.Tag, 323 Type: imap.StatusRespBad, 324 Info: err.Error(), 325 } 326 } else { 327 var err error 328 res, up, err = c.handleCommand(cmd) 329 if err != nil { 330 res = &imap.StatusResp{ 331 Tag: cmd.Tag, 332 Type: imap.StatusRespBad, 333 Info: err.Error(), 334 } 335 } 336 } 337 } 338 339 if res != nil { 340 341 if err := c.WriteResp(res); err != nil { 342 c.s.ErrorLog.Println("cannot write response:", err) 343 continue 344 } 345 346 if up != nil && res.Type == imap.StatusRespOk { 347 if err := up.Upgrade(c.conn); err != nil { 348 c.s.ErrorLog.Println("cannot upgrade connection:", err) 349 return err 350 } 351 } 352 } 353 } 354} 355 356func (c *conn) WaitReady() { 357 c.upgrade <- true 358 c.Conn.WaitReady() 359} 360 361func (c *conn) commandHandler(cmd *imap.Command) (hdlr Handler, err error) { 362 newHandler := c.s.Command(cmd.Name) 363 if newHandler == nil { 364 err = errors.New("Unknown command") 365 return 366 } 367 368 hdlr = newHandler() 369 err = hdlr.Parse(cmd.Arguments) 370 return 371} 372 373func (c *conn) handleCommand(cmd *imap.Command) (res *imap.StatusResp, up Upgrader, err error) { 374 hdlr, err := c.commandHandler(cmd) 375 if err != nil { 376 return 377 } 378 379 hdlrErr := hdlr.Handle(c.conn) 380 if statusErr, ok := hdlrErr.(*errStatusResp); ok { 381 res = statusErr.resp 382 } else if hdlrErr != nil { 383 res = &imap.StatusResp{ 384 Type: imap.StatusRespNo, 385 Info: hdlrErr.Error(), 386 } 387 } else { 388 res = &imap.StatusResp{ 389 Type: imap.StatusRespOk, 390 } 391 } 392 393 if res != nil { 394 res.Tag = cmd.Tag 395 396 if res.Type == imap.StatusRespOk && res.Info == "" { 397 res.Info = cmd.Name + " completed" 398 } 399 } 400 401 up, _ = hdlr.(Upgrader) 402 return 403} 404