action.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. package pop3_server
  2. import (
  3. "database/sql"
  4. "github.com/Jinnrry/gopop"
  5. log "github.com/sirupsen/logrus"
  6. "github.com/spf13/cast"
  7. "pmail/db"
  8. "pmail/models"
  9. "pmail/services/detail"
  10. "pmail/utils/array"
  11. "pmail/utils/context"
  12. "pmail/utils/errors"
  13. "pmail/utils/id"
  14. "pmail/utils/password"
  15. "strings"
  16. )
  17. type action struct {
  18. }
  19. // Custom 非标准命令
  20. func (a action) Custom(session *gopop.Session, cmd string, args []string) ([]string, error) {
  21. if session.Ctx == nil {
  22. tc := &context.Context{}
  23. tc.SetValue(context.LogID, id.GenLogID())
  24. session.Ctx = tc
  25. }
  26. log.WithContext(session.Ctx).Warnf("not supported cmd request! cmd:%s args:%v", cmd, args)
  27. return nil, nil
  28. }
  29. // Capa 说明服务端支持的命令列表
  30. func (a action) Capa(session *gopop.Session) ([]string, error) {
  31. if session.Ctx == nil {
  32. tc := &context.Context{}
  33. tc.SetValue(context.LogID, id.GenLogID())
  34. session.Ctx = tc
  35. }
  36. if session.InTls {
  37. log.WithContext(session.Ctx).Debugf("POP3 CMD: CAPA With Tls")
  38. } else {
  39. log.WithContext(session.Ctx).Debugf("POP3 CMD: CAPA Without Tls")
  40. }
  41. ret := []string{
  42. "USER",
  43. "PASS",
  44. "TOP",
  45. "APOP",
  46. "STAT",
  47. "UIDL",
  48. "LIST",
  49. "RETR",
  50. "DELE",
  51. "REST",
  52. "NOOP",
  53. "QUIT",
  54. }
  55. if !session.InTls {
  56. ret = append(ret, "STLS")
  57. }
  58. return ret, nil
  59. }
  60. // User 提交登陆的用户名
  61. func (a action) User(session *gopop.Session, username string) error {
  62. if session.Ctx == nil {
  63. tc := &context.Context{}
  64. tc.SetValue(context.LogID, id.GenLogID())
  65. session.Ctx = tc
  66. }
  67. log.WithContext(session.Ctx).Debugf("POP3 CMD: USER, Args:%s", username)
  68. infos := strings.Split(username, "@")
  69. if len(infos) > 1 {
  70. username = infos[0]
  71. }
  72. log.WithContext(session.Ctx).Debugf("POP3 User %s", username)
  73. session.User = username
  74. return nil
  75. }
  76. // Pass 提交密码验证
  77. func (a action) Pass(session *gopop.Session, pwd string) error {
  78. if session.Ctx == nil {
  79. tc := &context.Context{}
  80. tc.SetValue(context.LogID, id.GenLogID())
  81. session.Ctx = tc
  82. }
  83. log.WithContext(session.Ctx).Debugf("POP3 PASS %s , User:%s", pwd, session.User)
  84. var user models.User
  85. encodePwd := password.Encode(pwd)
  86. err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? and password =?"), session.User, encodePwd)
  87. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  88. log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
  89. }
  90. if user.ID > 0 {
  91. session.Status = gopop.TRANSACTION
  92. session.Ctx.(*context.Context).UserID = user.ID
  93. session.Ctx.(*context.Context).UserName = user.Name
  94. session.Ctx.(*context.Context).UserAccount = user.Account
  95. return nil
  96. }
  97. return errors.New("password error")
  98. }
  99. // Apop APOP登陆命令
  100. func (a action) Apop(session *gopop.Session, username, digest string) error {
  101. if session.Ctx == nil {
  102. tc := &context.Context{}
  103. tc.SetValue(context.LogID, id.GenLogID())
  104. session.Ctx = tc
  105. }
  106. log.WithContext(session.Ctx).Debugf("POP3 CMD: APOP, Args:%s,%s", username, digest)
  107. infos := strings.Split(username, "@")
  108. if len(infos) > 1 {
  109. username = infos[0]
  110. }
  111. log.WithContext(session.Ctx).Debugf("POP3 APOP %s %s", username, digest)
  112. var user models.User
  113. err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? "), username)
  114. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  115. log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
  116. }
  117. if user.ID > 0 && digest == password.Md5Encode(user.Password) {
  118. session.User = username
  119. session.Status = gopop.TRANSACTION
  120. session.Ctx.(*context.Context).UserID = user.ID
  121. session.Ctx.(*context.Context).UserName = user.Name
  122. session.Ctx.(*context.Context).UserAccount = user.Account
  123. return nil
  124. }
  125. return errors.New("password error")
  126. }
  127. type statInfo struct {
  128. Num int64 `json:"num"`
  129. Size int64 `json:"size"`
  130. }
  131. // Stat 查询邮件数量
  132. func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error) {
  133. log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
  134. var si statInfo
  135. 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"))
  136. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  137. log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
  138. err = nil
  139. log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT :0,0")
  140. return 0, 0, nil
  141. }
  142. log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", si.Num, si.Size)
  143. return si.Num, si.Size, nil
  144. }
  145. // Uidl 查询某封邮件的唯一标志符
  146. func (a action) Uidl(session *gopop.Session, msg string) ([]gopop.UidlItem, error) {
  147. log.WithContext(session.Ctx).Debugf("POP3 CMD: UIDL ,Args:%s", msg)
  148. reqId := cast.ToInt64(msg)
  149. if reqId > 0 {
  150. return []gopop.UidlItem{
  151. {
  152. Id: reqId,
  153. UnionId: msg,
  154. },
  155. }, nil
  156. }
  157. var res []listItem
  158. var err error
  159. var ssql string
  160. ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id FROM email where type = 0")
  161. err = db.Instance.Select(&res, ssql)
  162. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  163. log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
  164. err = nil
  165. return []gopop.UidlItem{}, nil
  166. }
  167. ret := []gopop.UidlItem{}
  168. for _, re := range res {
  169. ret = append(ret, gopop.UidlItem{
  170. Id: re.Id,
  171. UnionId: cast.ToString(re.Id),
  172. })
  173. }
  174. return ret, nil
  175. }
  176. type listItem struct {
  177. Id int64 `json:"id"`
  178. Size int64 `json:"size"`
  179. }
  180. // List 邮件列表
  181. func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, error) {
  182. log.WithContext(session.Ctx).Debugf("POP3 CMD: LIST ,Args:%s", msg)
  183. var res []listItem
  184. var listId int64
  185. if msg != "" {
  186. listId = cast.ToInt64(msg)
  187. if listId == 0 {
  188. return nil, errors.New("params error")
  189. }
  190. }
  191. var err error
  192. var ssql string
  193. if listId != 0 {
  194. ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where id =?")
  195. err = db.Instance.Select(&res, ssql, listId)
  196. } else {
  197. ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where type = 0")
  198. err = db.Instance.Select(&res, ssql)
  199. }
  200. if err != nil && !errors.Is(err, sql.ErrNoRows) {
  201. log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
  202. err = nil
  203. return []gopop.MailInfo{}, nil
  204. }
  205. ret := []gopop.MailInfo{}
  206. for _, re := range res {
  207. ret = append(ret, gopop.MailInfo{
  208. Id: re.Id,
  209. Size: re.Size,
  210. })
  211. }
  212. return ret, nil
  213. }
  214. // Retr 获取邮件详情
  215. func (a action) Retr(session *gopop.Session, id int64) (string, int64, error) {
  216. log.WithContext(session.Ctx).Debugf("POP3 CMD: RETR ,Args:%d", id)
  217. email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
  218. if err != nil {
  219. log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
  220. return "", 0, errors.New("server error")
  221. }
  222. ret := email.ToTransObj().BuildBytes(session.Ctx.(*context.Context), false)
  223. return string(ret), cast.ToInt64(len(ret)), nil
  224. }
  225. // Delete 删除邮件
  226. func (a action) Delete(session *gopop.Session, id int64) error {
  227. log.WithContext(session.Ctx).Debugf("POP3 CMD: DELE ,Args:%d", id)
  228. session.DeleteIds = append(session.DeleteIds, id)
  229. session.DeleteIds = array.Unique(session.DeleteIds)
  230. return nil
  231. }
  232. func (a action) Rest(session *gopop.Session) error {
  233. log.WithContext(session.Ctx).Debugf("POP3 CMD: REST ")
  234. session.DeleteIds = []int64{}
  235. return nil
  236. }
  237. func (a action) Top(session *gopop.Session, id int64, n int) (string, error) {
  238. log.WithContext(session.Ctx).Debugf("POP3 CMD: TOP %d %d", id, n)
  239. email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
  240. if err != nil {
  241. log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
  242. return "", errors.New("server error")
  243. }
  244. ret := email.ToTransObj().BuildBytes(session.Ctx.(*context.Context), false)
  245. res := strings.Split(string(ret), "\n")
  246. headerEndLine := len(res) - 1
  247. for i, re := range res {
  248. if re == "\r" {
  249. headerEndLine = i
  250. break
  251. }
  252. }
  253. if len(res) <= headerEndLine+n+1 {
  254. return string(ret), nil
  255. }
  256. return array.Join(res[0:headerEndLine+n+1], "\n"), nil
  257. }
  258. func (a action) Noop(session *gopop.Session) error {
  259. log.WithContext(session.Ctx).Debugf("POP3 CMD: NOOP ")
  260. return nil
  261. }
  262. func (a action) Quit(session *gopop.Session) error {
  263. log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
  264. if len(session.DeleteIds) > 0 {
  265. _, err := db.Instance.Exec(db.WithContext(session.Ctx.(*context.Context), "UPDATE email SET status=3 WHERE id in ?"), session.DeleteIds)
  266. if err != nil {
  267. log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
  268. }
  269. }
  270. return nil
  271. }