action.go 8.6 KB

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