mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-10-29 02:52:31 +08:00
330 lines
8.6 KiB
Go
330 lines
8.6 KiB
Go
package pop3_server
|
|
|
|
import (
|
|
"database/sql"
|
|
"github.com/Jinnrry/gopop"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cast"
|
|
"pmail/db"
|
|
"pmail/models"
|
|
"pmail/services/detail"
|
|
"pmail/utils/array"
|
|
"pmail/utils/context"
|
|
"pmail/utils/errors"
|
|
"pmail/utils/id"
|
|
"pmail/utils/password"
|
|
"strings"
|
|
)
|
|
|
|
type action struct {
|
|
}
|
|
|
|
// Custom 非标准命令
|
|
func (a action) Custom(session *gopop.Session, cmd string, args []string) ([]string, error) {
|
|
if session.Ctx == nil {
|
|
tc := &context.Context{}
|
|
tc.SetValue(context.LogID, id.GenLogID())
|
|
session.Ctx = tc
|
|
}
|
|
|
|
log.WithContext(session.Ctx).Warnf("not supported cmd request! cmd:%s args:%v", cmd, args)
|
|
return nil, nil
|
|
}
|
|
|
|
// Capa 说明服务端支持的命令列表
|
|
func (a action) Capa(session *gopop.Session) ([]string, error) {
|
|
if session.Ctx == nil {
|
|
tc := &context.Context{}
|
|
tc.SetValue(context.LogID, id.GenLogID())
|
|
session.Ctx = tc
|
|
}
|
|
|
|
if session.InTls {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: CAPA With Tls")
|
|
} else {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: CAPA Without Tls")
|
|
}
|
|
|
|
ret := []string{
|
|
"USER",
|
|
"PASS",
|
|
"TOP",
|
|
"APOP",
|
|
"STAT",
|
|
"UIDL",
|
|
"LIST",
|
|
"RETR",
|
|
"DELE",
|
|
"REST",
|
|
"NOOP",
|
|
"QUIT",
|
|
}
|
|
if !session.InTls {
|
|
ret = append(ret, "STLS")
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// User 提交登陆的用户名
|
|
func (a action) User(session *gopop.Session, username string) error {
|
|
if session.Ctx == nil {
|
|
tc := &context.Context{}
|
|
tc.SetValue(context.LogID, id.GenLogID())
|
|
session.Ctx = tc
|
|
}
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: USER, Args:%s", username)
|
|
|
|
infos := strings.Split(username, "@")
|
|
if len(infos) > 1 {
|
|
username = infos[0]
|
|
}
|
|
|
|
log.WithContext(session.Ctx).Debugf("POP3 User %s", username)
|
|
|
|
session.User = username
|
|
return nil
|
|
}
|
|
|
|
// Pass 提交密码验证
|
|
func (a action) Pass(session *gopop.Session, pwd string) error {
|
|
if session.Ctx == nil {
|
|
tc := &context.Context{}
|
|
tc.SetValue(context.LogID, id.GenLogID())
|
|
session.Ctx = tc
|
|
}
|
|
|
|
log.WithContext(session.Ctx).Debugf("POP3 PASS %s , User:%s", pwd, session.User)
|
|
|
|
var user models.User
|
|
|
|
encodePwd := password.Encode(pwd)
|
|
|
|
err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? and password =?"), session.User, encodePwd)
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
}
|
|
|
|
if user.ID > 0 {
|
|
session.Status = gopop.TRANSACTION
|
|
|
|
session.Ctx.(*context.Context).UserID = user.ID
|
|
session.Ctx.(*context.Context).UserName = user.Name
|
|
session.Ctx.(*context.Context).UserAccount = user.Account
|
|
|
|
return nil
|
|
}
|
|
|
|
return errors.New("password error")
|
|
}
|
|
|
|
// Apop APOP登陆命令
|
|
func (a action) Apop(session *gopop.Session, username, digest string) error {
|
|
if session.Ctx == nil {
|
|
tc := &context.Context{}
|
|
tc.SetValue(context.LogID, id.GenLogID())
|
|
session.Ctx = tc
|
|
}
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: APOP, Args:%s,%s", username, digest)
|
|
|
|
infos := strings.Split(username, "@")
|
|
if len(infos) > 1 {
|
|
username = infos[0]
|
|
}
|
|
|
|
log.WithContext(session.Ctx).Debugf("POP3 APOP %s %s", username, digest)
|
|
|
|
var user models.User
|
|
|
|
err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? "), username)
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
}
|
|
|
|
if user.ID > 0 && digest == password.Md5Encode(user.Password) {
|
|
session.User = username
|
|
session.Status = gopop.TRANSACTION
|
|
|
|
session.Ctx.(*context.Context).UserID = user.ID
|
|
session.Ctx.(*context.Context).UserName = user.Name
|
|
session.Ctx.(*context.Context).UserAccount = user.Account
|
|
|
|
return nil
|
|
}
|
|
|
|
return errors.New("password error")
|
|
|
|
}
|
|
|
|
type statInfo struct {
|
|
Num int64 `json:"num"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
// Stat 查询邮件数量
|
|
func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error) {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
|
|
|
|
var si statInfo
|
|
err = db.Instance.Get(&si, db.WithContext(session.Ctx.(*context.Context), "select count(1) as `num`, sum(length(text)+length(html)) as `size` from email where type = 0"))
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
err = nil
|
|
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT :0,0")
|
|
return 0, 0, nil
|
|
}
|
|
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", si.Num, si.Size)
|
|
|
|
return si.Num, si.Size, nil
|
|
}
|
|
|
|
func (a action) Uidl(session *gopop.Session, msg string) ([]gopop.UidlItem, error) {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: UIDL ,Args:%s", msg)
|
|
|
|
reqId := cast.ToInt64(msg)
|
|
if reqId > 0 {
|
|
return []gopop.UidlItem{
|
|
{
|
|
Id: reqId,
|
|
UnionId: msg,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var res []listItem
|
|
|
|
var err error
|
|
var ssql string
|
|
|
|
ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id FROM email")
|
|
err = db.Instance.Select(&res, ssql)
|
|
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
|
|
err = nil
|
|
return []gopop.UidlItem{}, nil
|
|
}
|
|
ret := []gopop.UidlItem{}
|
|
for _, re := range res {
|
|
ret = append(ret, gopop.UidlItem{
|
|
Id: re.Id,
|
|
UnionId: cast.ToString(re.Id),
|
|
})
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
type listItem struct {
|
|
Id int64 `json:"id"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
// List 邮件列表
|
|
func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, error) {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: LIST ,Args:%s", msg)
|
|
var res []listItem
|
|
var listId int64
|
|
if msg != "" {
|
|
listId = cast.ToInt64(msg)
|
|
if listId == 0 {
|
|
return nil, errors.New("params error")
|
|
}
|
|
}
|
|
var err error
|
|
var ssql string
|
|
|
|
if listId != 0 {
|
|
ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where id =?")
|
|
err = db.Instance.Select(&res, ssql, listId)
|
|
} else {
|
|
ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where type = 0")
|
|
err = db.Instance.Select(&res, ssql)
|
|
}
|
|
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
|
|
err = nil
|
|
return []gopop.MailInfo{}, nil
|
|
}
|
|
ret := []gopop.MailInfo{}
|
|
for _, re := range res {
|
|
ret = append(ret, gopop.MailInfo{
|
|
Id: re.Id,
|
|
Size: re.Size,
|
|
})
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// Retr 获取邮件详情
|
|
func (a action) Retr(session *gopop.Session, id int64) (string, int64, error) {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: RETR ,Args:%d", id)
|
|
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
|
|
if err != nil {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
return "", 0, errors.New("server error")
|
|
}
|
|
|
|
ret := email.ToTransObj().BuildBytes(session.Ctx.(*context.Context), false)
|
|
return string(ret), cast.ToInt64(len(ret)), nil
|
|
|
|
}
|
|
|
|
// Delete 删除邮件
|
|
func (a action) Delete(session *gopop.Session, id int64) error {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: DELE ,Args:%d", id)
|
|
|
|
session.DeleteIds = append(session.DeleteIds, id)
|
|
session.DeleteIds = array.Unique(session.DeleteIds)
|
|
return nil
|
|
}
|
|
|
|
func (a action) Rest(session *gopop.Session) error {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: REST ")
|
|
session.DeleteIds = []int64{}
|
|
return nil
|
|
}
|
|
|
|
func (a action) Top(session *gopop.Session, id int64, n int) (string, error) {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: TOP %d %d", id, n)
|
|
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
|
|
if err != nil {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
return "", errors.New("server error")
|
|
}
|
|
|
|
ret := email.ToTransObj().BuildBytes(session.Ctx.(*context.Context), false)
|
|
res := strings.Split(string(ret), "\n")
|
|
headerEndLine := len(res) - 1
|
|
for i, re := range res {
|
|
if re == "\r" {
|
|
headerEndLine = i
|
|
break
|
|
}
|
|
}
|
|
if len(res) <= headerEndLine+n+1 {
|
|
return string(ret), nil
|
|
}
|
|
|
|
return array.Join(res[0:headerEndLine+n+1], "\n"), nil
|
|
|
|
}
|
|
|
|
func (a action) Noop(session *gopop.Session) error {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: NOOP ")
|
|
return nil
|
|
}
|
|
|
|
func (a action) Quit(session *gopop.Session) error {
|
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
|
|
if len(session.DeleteIds) > 0 {
|
|
|
|
_, err := db.Instance.Exec(db.WithContext(session.Ctx.(*context.Context), "DELETE FROM email WHERE id in ?"), session.DeleteIds)
|
|
if err != nil {
|
|
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|