action.go 8.7 KB

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