831 lines
20 KiB
Go
831 lines
20 KiB
Go
package goimap
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/Jinnrry/pmail/utils/context"
|
|
"github.com/Jinnrry/pmail/utils/id"
|
|
log "github.com/sirupsen/logrus"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
eol = "\r\n"
|
|
)
|
|
|
|
// Server Imap服务实例
|
|
type Server struct {
|
|
Domain string // 域名
|
|
Port int // 端口
|
|
TlsEnabled bool //是否启用Tls
|
|
TlsConfig *tls.Config // tls配置
|
|
ConnectAliveTime time.Duration // 连接存活时间,默认不超时
|
|
Action Action
|
|
stop chan bool
|
|
close bool
|
|
lck sync.Mutex
|
|
}
|
|
|
|
// NewImapServer 新建一个服务实例
|
|
func NewImapServer(port int, domain string, tlsEnabled bool, tlsConfig *tls.Config, action Action) *Server {
|
|
return &Server{
|
|
Domain: domain,
|
|
Port: port,
|
|
TlsEnabled: tlsEnabled,
|
|
TlsConfig: tlsConfig,
|
|
Action: action,
|
|
stop: make(chan bool, 1),
|
|
}
|
|
}
|
|
|
|
// Start 启动服务
|
|
func (s *Server) Start() error {
|
|
if !s.TlsEnabled {
|
|
return s.startWithoutTLS()
|
|
} else {
|
|
return s.startWithTLS()
|
|
}
|
|
}
|
|
|
|
func (s *Server) startWithTLS() error {
|
|
if s.lck.TryLock() {
|
|
listener, err := tls.Listen("tcp", fmt.Sprintf(":%d", s.Port), s.TlsConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.close = false
|
|
defer func() {
|
|
listener.Close()
|
|
}()
|
|
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
if s.close {
|
|
break
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
go s.handleClient(conn)
|
|
}
|
|
}()
|
|
<-s.stop
|
|
} else {
|
|
return errors.New("Server Is Running")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) startWithoutTLS() error {
|
|
if s.lck.TryLock() {
|
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.close = false
|
|
defer func() {
|
|
listener.Close()
|
|
}()
|
|
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
if s.close {
|
|
break
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
go s.handleClient(conn)
|
|
}
|
|
}()
|
|
<-s.stop
|
|
} else {
|
|
return errors.New("Server Is Running")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop 停止服务
|
|
func (s *Server) Stop() {
|
|
s.close = true
|
|
s.stop <- true
|
|
}
|
|
|
|
func (s *Server) authenticate(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if args == "LOGIN" {
|
|
write(session, "+ VXNlciBOYW1lAA=="+eol, "")
|
|
line, err2 := reader.ReadString('\n')
|
|
if err2 != nil {
|
|
if conn != nil {
|
|
_ = conn.Close()
|
|
}
|
|
session.Conn = nil
|
|
session.IN_IDLE = false
|
|
return
|
|
}
|
|
account, err := base64.StdEncoding.DecodeString(line)
|
|
if err != nil {
|
|
showBad(session, "Data Error.", nub)
|
|
return
|
|
}
|
|
write(session, "+ UGFzc3dvcmQA"+eol, "")
|
|
line, err = reader.ReadString('\n')
|
|
if err2 != nil {
|
|
if conn != nil {
|
|
_ = conn.Close()
|
|
}
|
|
session.Conn = nil
|
|
session.IN_IDLE = false
|
|
return
|
|
}
|
|
password, err := base64.StdEncoding.DecodeString(line)
|
|
res := s.Action.Login(session, string(account), string(password))
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
} else {
|
|
showBad(session, "Unsupported AUTHENTICATE mechanism.", nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) capability(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
res := s.Action.CapaBility(session)
|
|
if res.Type == BAD {
|
|
write(session, fmt.Sprintf("* BAD %s%s", res.Message, eol), nub)
|
|
} else {
|
|
ret := "*"
|
|
for _, command := range res.Data {
|
|
ret += " " + command
|
|
}
|
|
ret += eol
|
|
write(session, ret, nub)
|
|
showSucc(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) create(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "CREATE", nub)
|
|
return
|
|
}
|
|
res := s.Action.Create(session, args)
|
|
showSucc(session, res.Message, nub)
|
|
}
|
|
|
|
func (s *Server) delete(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "DELETE", nub)
|
|
return
|
|
}
|
|
res := s.Action.Delete(session, args)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) rename(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "RENAME", nub)
|
|
} else {
|
|
dt := strings.Split(args, " ")
|
|
res := s.Action.Rename(session, dt[0], dt[1])
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) list(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "LIST", nub)
|
|
} else {
|
|
dt := strings.Split(args, " ")
|
|
dt[0] = strings.Trim(dt[0], `"`)
|
|
dt[1] = strings.Trim(dt[1], `"`)
|
|
res := s.Action.List(session, dt[0], dt[1])
|
|
if res.Type == SUCCESS {
|
|
showSuccWithData(session, res.Data, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) append(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
log.WithContext(session.Ctx).Debugf("Append: %+v", args)
|
|
}
|
|
|
|
func (s *Server) cselect(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
res := s.Action.Select(session, args)
|
|
if res.Type == SUCCESS {
|
|
showSuccWithData(session, res.Data, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) fetch(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader, uid bool) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "FETCH", nub)
|
|
} else {
|
|
dt := strings.SplitN(args, " ", 2)
|
|
if len(dt) != 2 {
|
|
showBad(session, "Error Params", nub)
|
|
return
|
|
}
|
|
|
|
res := s.Action.Fetch(session, dt[0], dt[1], uid)
|
|
if res.Type == SUCCESS {
|
|
showSuccWithData(session, res.Data, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) store(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader, uid bool) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "RENAME", nub)
|
|
} else {
|
|
dt := strings.SplitN(args, " ", 2)
|
|
res := s.Action.Store(session, dt[0], dt[1], uid)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) cclose(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
res := s.Action.Close(session)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) expunge(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
res := s.Action.Expunge(session)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) examine(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "EXAMINE", nub)
|
|
}
|
|
res := s.Action.Examine(session, args)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) unsubscribe(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "UNSUBSCRIBE", nub)
|
|
} else {
|
|
res := s.Action.UnSubscribe(session, args)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) lsub(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "LSUB", nub)
|
|
} else {
|
|
dt := strings.Split(args, " ")
|
|
dt[0] = strings.Trim(dt[0], `"`)
|
|
res := s.Action.LSub(session, dt[0], dt[1])
|
|
if res.Type == SUCCESS {
|
|
showSuccWithData(session, res.Data, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) status(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "STATUS", nub)
|
|
} else {
|
|
var mailBox string
|
|
var params []string
|
|
if strings.HasPrefix(args, `"`) {
|
|
dt := strings.Split(args, `"`)
|
|
if len(dt) >= 3 {
|
|
mailBox = dt[1]
|
|
}
|
|
dt[2] = strings.Trim(dt[2], "() ")
|
|
params = strings.Split(dt[2], " ")
|
|
} else {
|
|
dt := strings.SplitN(args, " ", 2)
|
|
dt[0] = strings.ReplaceAll(dt[0], `"`, "")
|
|
dt[1] = strings.Trim(dt[1], "()")
|
|
mailBox = dt[0]
|
|
params = strings.Split(dt[1], " ")
|
|
}
|
|
|
|
res := s.Action.Status(session, mailBox, params)
|
|
if res.Type == SUCCESS {
|
|
showSuccWithData(session, res.Data, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) check(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
res := s.Action.Check(session)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) search(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader, uid bool) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "SEARCH", nub)
|
|
} else {
|
|
var res CommandResponse
|
|
if args == "ALL" {
|
|
res = s.Action.Search(session, "", "UID 1:*", uid)
|
|
} else {
|
|
res = s.Action.Search(session, "", args, uid)
|
|
}
|
|
|
|
if res.Type == SUCCESS {
|
|
content := "* SEARCH"
|
|
for _, datum := range res.Data {
|
|
content += " " + datum
|
|
}
|
|
content += eol
|
|
content += fmt.Sprintf("%s OK SEARCH completed (Success)%s", nub, eol)
|
|
write(session, content, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) copy(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "COPY", nub)
|
|
} else {
|
|
dt := strings.SplitN(args, " ", 2)
|
|
res := s.Action.Copy(session, dt[0], dt[1])
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) noop(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
res := s.Action.Noop(session)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) login(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if args == "" {
|
|
paramsErr(session, "LOGIN", nub)
|
|
} else {
|
|
dt := strings.SplitN(args, " ", 2)
|
|
res := s.Action.Login(session, strings.Trim(dt[0], `"`), strings.Trim(dt[1], `"`))
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) logout(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
res := s.Action.Logout(session)
|
|
write(session, "* BYE PMail Server logging out"+eol, nub)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
if conn != nil {
|
|
_ = conn.Close()
|
|
}
|
|
}
|
|
|
|
func (s *Server) unselect(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
res := s.Action.Unselect(session)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) subscribe(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
if args == "" {
|
|
paramsErr(session, "SUBSCRIBE", nub)
|
|
} else {
|
|
res := s.Action.Subscribe(session, args)
|
|
if res.Type == SUCCESS {
|
|
showSucc(session, res.Message, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) idle(session *Session, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
if session.Status != AUTHORIZED {
|
|
showBad(session, "Need Login", nub)
|
|
return
|
|
}
|
|
session.IN_IDLE = true
|
|
res := s.Action.IDLE(session)
|
|
if res.Type == SUCCESS {
|
|
write(session, "+ idling"+eol, nub)
|
|
} else if res.Type == BAD {
|
|
showBad(session, res.Message, nub)
|
|
} else {
|
|
showNo(session, res.Message, nub)
|
|
}
|
|
}
|
|
|
|
func (s *Server) custom(session *Session, cmd string, args string, nub string, conn net.Conn, reader *bufio.Reader) {
|
|
res := s.Action.Custom(session, cmd, args)
|
|
if res.Type == BAD {
|
|
write(session, fmt.Sprintf("* BAD %s %s", res.Message, eol), nub)
|
|
} else if res.Type == NO {
|
|
showNo(session, res.Message, nub)
|
|
} else {
|
|
if len(res.Data) == 0 {
|
|
showSucc(session, res.Message, nub)
|
|
} else {
|
|
ret := ""
|
|
for _, re := range res.Data {
|
|
ret += fmt.Sprintf("%s%s", re, eol)
|
|
}
|
|
ret += "." + eol
|
|
write(session, fmt.Sprintf(ret), nub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Server) doCommand(session *Session, rawLine string, conn net.Conn, reader *bufio.Reader) {
|
|
nub, cmd, args := getCommand(rawLine)
|
|
log.WithContext(session.Ctx).Debugf("Imap Input:\t %s", rawLine)
|
|
if cmd != "IDLE" {
|
|
session.IN_IDLE = false
|
|
}
|
|
|
|
switch cmd {
|
|
case "":
|
|
if conn != nil {
|
|
conn.Close()
|
|
conn = nil
|
|
}
|
|
break
|
|
|
|
case "AUTHENTICATE":
|
|
s.authenticate(session, args, nub, conn, reader)
|
|
case "CAPABILITY":
|
|
s.capability(session, rawLine, nub, conn, reader)
|
|
case "CREATE":
|
|
s.create(session, args, nub, conn, reader)
|
|
case "DELETE":
|
|
s.delete(session, args, nub, conn, reader)
|
|
case "RENAME":
|
|
s.rename(session, args, nub, conn, reader)
|
|
case "LIST":
|
|
s.list(session, args, nub, conn, reader)
|
|
case "APPEND":
|
|
s.append(session, args, nub, conn, reader)
|
|
case "SELECT":
|
|
s.cselect(session, args, nub, conn, reader)
|
|
case "FETCH":
|
|
s.fetch(session, args, nub, conn, reader, false)
|
|
case "UID FETCH":
|
|
s.fetch(session, args, nub, conn, reader, true)
|
|
case "STORE":
|
|
s.store(session, args, nub, conn, reader, false)
|
|
case "UID STORE":
|
|
s.store(session, args, nub, conn, reader, true)
|
|
case "CLOSE":
|
|
s.cclose(session, args, nub, conn, reader)
|
|
case "EXPUNGE":
|
|
s.expunge(session, args, nub, conn, reader)
|
|
case "EXAMINE":
|
|
s.examine(session, args, nub, conn, reader)
|
|
case "SUBSCRIBE":
|
|
s.subscribe(session, args, nub, conn, reader)
|
|
case "UNSUBSCRIBE":
|
|
s.unsubscribe(session, args, nub, conn, reader)
|
|
case "LSUB":
|
|
s.lsub(session, args, nub, conn, reader)
|
|
case "STATUS":
|
|
s.status(session, args, nub, conn, reader)
|
|
case "CHECK":
|
|
s.check(session, args, nub, conn, reader)
|
|
case "SEARCH":
|
|
s.search(session, args, nub, conn, reader, false)
|
|
case "UID SEARCH":
|
|
s.search(session, args, nub, conn, reader, true)
|
|
case "COPY":
|
|
s.copy(session, args, nub, conn, reader)
|
|
case "NOOP":
|
|
s.noop(session, args, nub, conn, reader)
|
|
case "LOGIN":
|
|
s.login(session, args, nub, conn, reader)
|
|
case "LOGOUT":
|
|
s.logout(session, args, nub, conn, reader)
|
|
case "UNSELECT":
|
|
s.unselect(session, args, nub, conn, reader)
|
|
case "IDLE":
|
|
s.idle(session, args, nub, conn, reader)
|
|
default:
|
|
s.custom(session, cmd, args, nub, conn, reader)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleClient(conn net.Conn) {
|
|
|
|
defer func() {
|
|
if conn != nil {
|
|
_ = conn.Close()
|
|
}
|
|
}()
|
|
|
|
session := &Session{
|
|
Conn: conn,
|
|
Status: UNAUTHORIZED,
|
|
AliveTime: time.Now(),
|
|
}
|
|
|
|
tc := &context.Context{}
|
|
tc.SetValue(context.LogID, id.GenLogID())
|
|
session.Ctx = tc
|
|
|
|
if s.TlsEnabled && s.TlsConfig != nil {
|
|
session.InTls = true
|
|
}
|
|
|
|
// 检查连接是否超时
|
|
if s.ConnectAliveTime != 0 {
|
|
go func() {
|
|
for {
|
|
if time.Now().Sub(session.AliveTime) >= s.ConnectAliveTime {
|
|
if session.Conn != nil {
|
|
write(session, "* BYE AutoLogout; idle for too long", "")
|
|
_ = session.Conn.Close()
|
|
}
|
|
session.Conn = nil
|
|
session.IN_IDLE = false
|
|
return
|
|
}
|
|
time.Sleep(3 * time.Second)
|
|
}
|
|
}()
|
|
}
|
|
|
|
reader := bufio.NewReader(conn)
|
|
write(session, fmt.Sprintf(`* OK [CAPABILITY IMAP4 IMAP4rev1 AUTH=LOGIN] PMail Server ready%s`, eol), "")
|
|
|
|
for {
|
|
rawLine, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
if conn != nil {
|
|
_ = conn.Close()
|
|
}
|
|
session.Conn = nil
|
|
session.IN_IDLE = false
|
|
return
|
|
}
|
|
session.AliveTime = time.Now()
|
|
|
|
s.doCommand(session, rawLine, conn, reader)
|
|
|
|
}
|
|
}
|
|
|
|
// cuts the line into command and arguments
|
|
func getCommand(line string) (string, string, string) {
|
|
line = strings.Trim(line, "\r \n")
|
|
cmd := strings.SplitN(line, " ", 3)
|
|
if len(cmd) == 1 {
|
|
return "", "", ""
|
|
}
|
|
|
|
if len(cmd) == 3 {
|
|
if strings.ToTitle(cmd[1]) == "UID" {
|
|
args := strings.SplitN(cmd[2], " ", 2)
|
|
if len(args) >= 2 {
|
|
return cmd[0], strings.ToTitle(cmd[1]) + " " + strings.ToTitle(args[0]), args[1]
|
|
}
|
|
}
|
|
|
|
return cmd[0], strings.ToTitle(cmd[1]), cmd[2]
|
|
}
|
|
|
|
return cmd[0], strings.ToTitle(cmd[1]), ""
|
|
}
|
|
|
|
func getSafeArg(args []string, nr int) string {
|
|
if nr < len(args) {
|
|
return args[nr]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func showSucc(s *Session, msg, nub string) {
|
|
if msg == "" {
|
|
write(s, fmt.Sprintf("%s OK success %s", nub, eol), nub)
|
|
} else {
|
|
write(s, fmt.Sprintf("%s %s %s", nub, msg, eol), nub)
|
|
}
|
|
}
|
|
|
|
func showSuccWithData(s *Session, data []string, msg string, nub string) {
|
|
content := ""
|
|
for _, datum := range data {
|
|
content += fmt.Sprintf("%s%s", datum, eol)
|
|
}
|
|
content += fmt.Sprintf("%s OK %s%s", nub, msg, eol)
|
|
write(s, content, nub)
|
|
}
|
|
|
|
func showBad(s *Session, err string, nub string) {
|
|
if nub == "" {
|
|
nub = "*"
|
|
}
|
|
|
|
if err == "" {
|
|
write(s, fmt.Sprintf("%s BAD %s", nub, eol), nub)
|
|
return
|
|
}
|
|
write(s, fmt.Sprintf("%s BAD %s%s", nub, err, eol), nub)
|
|
}
|
|
|
|
func showNo(s *Session, msg string, nub string) {
|
|
write(s, fmt.Sprintf("%s NO %s%s", nub, msg, eol), nub)
|
|
}
|
|
|
|
func paramsErr(session *Session, commend string, nub string) {
|
|
write(session, fmt.Sprintf("* BAD %s parameters! %s", commend, eol), nub)
|
|
}
|
|
|
|
func write(session *Session, content string, nub string) {
|
|
if !strings.HasSuffix(content, eol) {
|
|
log.WithContext(session.Ctx).Errorf("Error:返回结尾错误 %s", content)
|
|
}
|
|
log.WithContext(session.Ctx).Debugf("Imap Out:\t |%s", content)
|
|
fmt.Fprintf(session.Conn, content)
|
|
}
|