action.go 8.3 KB

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