email.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package sender
  15. import (
  16. "context"
  17. "crypto/tls"
  18. "encoding/base64"
  19. "fmt"
  20. "io"
  21. "mime"
  22. "net/http"
  23. "net/url"
  24. "strings"
  25. "time"
  26. gomail "gopkg.in/mail.v2"
  27. "yunion.io/x/jsonutils"
  28. "yunion.io/x/log"
  29. "yunion.io/x/pkg/errors"
  30. "yunion.io/x/pkg/util/httputils"
  31. api "yunion.io/x/onecloud/pkg/apis/notify"
  32. "yunion.io/x/onecloud/pkg/notify/models"
  33. )
  34. type errorMap map[string]error
  35. func (em errorMap) Error() string {
  36. msg := make(map[string]string)
  37. for k, e := range em {
  38. msg[k] = e.Error()
  39. }
  40. return jsonutils.Marshal(msg).String()
  41. }
  42. type SEmailSender struct {
  43. config map[string]api.SNotifyConfigContent
  44. }
  45. func (emailSender *SEmailSender) GetSenderType() string {
  46. return api.EMAIL
  47. }
  48. func (emailSender *SEmailSender) Send(ctx context.Context, args api.SendParams) error {
  49. // 初始化emaliClient
  50. hostNmae, hostPort, userName, password := models.ConfigMap[api.EMAIL].Content.Hostname, models.ConfigMap[api.EMAIL].Content.Hostport, models.ConfigMap[api.EMAIL].Content.Username, models.ConfigMap[api.EMAIL].Content.Password
  51. dialer := gomail.NewDialer(hostNmae, hostPort, userName, password)
  52. // 是否支持ssl
  53. if models.ConfigMap[api.EMAIL].Content.SslGlobal {
  54. dialer.SSL = true
  55. } else {
  56. dialer.SSL = false
  57. dialer.TLSConfig = &tls.Config{
  58. InsecureSkipVerify: true,
  59. }
  60. }
  61. if len(args.EmailMsg.To) > 0 {
  62. // emailMsg不为空时
  63. sender, err := dialer.Dial()
  64. if err != nil {
  65. return errors.Wrap(err, "dialer.Dial")
  66. }
  67. retErr := errorMap{}
  68. destMap := make(map[string]int)
  69. for _, tos := range [][]string{
  70. args.EmailMsg.To,
  71. args.EmailMsg.Cc,
  72. args.EmailMsg.Bcc,
  73. } {
  74. for _, to := range tos {
  75. to = strings.ToLower(to)
  76. if _, ok := destMap[to]; !ok && len(to) > 0 {
  77. destMap[to] = 1
  78. }
  79. }
  80. }
  81. for to := range destMap {
  82. gmsg := gomail.NewMessage()
  83. gmsg.SetHeader("From", models.ConfigMap[api.EMAIL].Content.SenderAddress)
  84. gmsg.SetHeader("To", to)
  85. gmsg.SetHeader("Subject", args.EmailMsg.Subject)
  86. gmsg.SetBody("text/html", args.EmailMsg.Body)
  87. for i := range args.EmailMsg.Attachments {
  88. attach := args.EmailMsg.Attachments[i]
  89. gmsg.Attach(attach.Filename,
  90. gomail.SetCopyFunc(func(w io.Writer) error {
  91. mime := attach.Mime
  92. if len(mime) == 0 {
  93. mime = "application/octet-stream"
  94. }
  95. _, err := w.Write([]byte("Content-Type: " + attach.Mime))
  96. return errors.Wrap(err, "WriteMime")
  97. }),
  98. gomail.SetHeader(map[string][]string{
  99. "Content-Disposition": {
  100. fmt.Sprintf(`attachment; filename="%s"`, mime.QEncoding.Encode("UTF-8", attach.Filename)),
  101. },
  102. }),
  103. gomail.SetCopyFunc(func(w io.Writer) error {
  104. contBytes, err := base64.StdEncoding.DecodeString(attach.Base64Content)
  105. if err != nil {
  106. return errors.Wrap(err, "base64.StdEncoding.DecodeString")
  107. }
  108. _, err = w.Write(contBytes)
  109. return errors.Wrap(err, "WriteContent")
  110. }),
  111. )
  112. }
  113. errs := make([]error, 0)
  114. for tryTime := 3; tryTime > 0; tryTime-- {
  115. err = gomail.Send(sender, gmsg)
  116. log.Debugf("send %s to %s email err: %v", args.EmailMsg.Subject, to, err)
  117. if err != nil {
  118. errs = append(errs, errors.Wrapf(err, "Send"))
  119. time.Sleep(time.Second * 10)
  120. continue
  121. }
  122. errs = errs[0:0]
  123. break
  124. }
  125. if len(errs) > 0 {
  126. retErr[to] = errors.NewAggregate(errs)
  127. }
  128. }
  129. if len(retErr) > 0 {
  130. log.Errorf("send email error:%v", jsonutils.Marshal(retErr))
  131. return errors.Wrap(retErr, "send email")
  132. }
  133. } else {
  134. // 构造email发送请求
  135. gmsg := gomail.NewMessage()
  136. gmsg.SetHeader("From", models.ConfigMap[api.EMAIL].Content.SenderAddress)
  137. gmsg.SetHeader("To", args.Receivers.Contact)
  138. gmsg.SetHeader("Subject", args.Title)
  139. gmsg.SetBody("text/html", args.EmailMsg.Body)
  140. dialer.StartTLSPolicy = gomail.MandatoryStartTLS
  141. if err := dialer.DialAndSend(gmsg); err != nil {
  142. return errors.Wrap(err, "send email")
  143. }
  144. }
  145. return nil
  146. }
  147. func (emailSender *SEmailSender) ValidateConfig(ctx context.Context, config api.NotifyConfig) (string, error) {
  148. errChan := make(chan error, 1)
  149. go func() {
  150. dialer := gomail.NewDialer(config.Hostname, config.Hostport, config.Username, config.Password)
  151. if config.SslGlobal {
  152. dialer.SSL = true
  153. } else {
  154. dialer.SSL = false
  155. // StartLSConfig
  156. dialer.TLSConfig = &tls.Config{
  157. InsecureSkipVerify: true,
  158. }
  159. }
  160. sender, err := dialer.Dial()
  161. if err != nil {
  162. errChan <- err
  163. return
  164. }
  165. sender.Close()
  166. errChan <- nil
  167. }()
  168. ticker := time.Tick(10 * time.Second)
  169. select {
  170. case <-ticker:
  171. return "", errors.Error("timeout")
  172. case err := <-errChan:
  173. return "", err
  174. }
  175. }
  176. func (emailSender *SEmailSender) ContactByMobile(ctx context.Context, mobile, domainId string) (string, error) {
  177. return "", nil
  178. }
  179. func (emailSender *SEmailSender) IsPersonal() bool {
  180. return true
  181. }
  182. func (emailSender *SEmailSender) IsRobot() bool {
  183. return false
  184. }
  185. func (emailSender *SEmailSender) IsValid() bool {
  186. return len(emailSender.config) > 0
  187. }
  188. func (emailSender *SEmailSender) IsPullType() bool {
  189. return false
  190. }
  191. func (emailSender *SEmailSender) IsSystemConfigContactType() bool {
  192. return true
  193. }
  194. func (emailSender *SEmailSender) GetAccessToken(ctx context.Context, key string) error {
  195. return nil
  196. }
  197. func (emailSender *SEmailSender) sendMessageWithToken(ctx context.Context, uri string, method httputils.THttpMethod, header http.Header, params url.Values, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  198. if params == nil {
  199. params = url.Values{}
  200. }
  201. params.Set("access_token", models.ConfigMap[api.WORKWX].Content.AccessToken)
  202. return sendRequest(ctx, uri, httputils.POST, nil, params, jsonutils.Marshal(body))
  203. }
  204. func (emailSender *SEmailSender) RegisterConfig(config models.SConfig) {
  205. models.ConfigMap[config.Type] = config
  206. }
  207. func init() {
  208. models.Register(&SEmailSender{
  209. config: map[string]api.SNotifyConfigContent{},
  210. })
  211. }
  212. /*
  213. func SendEmail(conf *api.SEmailConfig, msg *api.SEmailMessage) error {
  214. dialer := gomail.NewDialer(conf.Hostname, conf.Hostport, conf.Username, conf.Password)
  215. if conf.SslGlobal {
  216. dialer.SSL = true
  217. } else {
  218. dialer.SSL = false
  219. dialer.TLSConfig = &tls.Config{
  220. InsecureSkipVerify: true,
  221. }
  222. }
  223. sender, err := dialer.Dial()
  224. if err != nil {
  225. return errors.Wrap(err, "dialer.Dial")
  226. }
  227. retErr := errorMap{}
  228. destMap := make(map[string]int)
  229. for _, tos := range [][]string{
  230. msg.To,
  231. msg.Cc,
  232. msg.Bcc,
  233. } {
  234. for _, to := range tos {
  235. to = strings.ToLower(to)
  236. if _, ok := destMap[to]; !ok {
  237. destMap[to] = 1
  238. }
  239. }
  240. }
  241. for to := range destMap {
  242. log.Debugf("send to %s %s", to, msg.Subject)
  243. gmsg := gomail.NewMessage()
  244. gmsg.SetHeader("From", conf.SenderAddress)
  245. gmsg.SetHeader("To", to)
  246. gmsg.SetHeader("Subject", msg.Subject)
  247. gmsg.SetBody("text/html", msg.Body)
  248. for i := range msg.Attachments {
  249. attach := msg.Attachments[i]
  250. gmsg.Attach(attach.Filename,
  251. gomail.SetCopyFunc(func(w io.Writer) error {
  252. mime := attach.Mime
  253. if len(mime) == 0 {
  254. mime = "application/octet-stream"
  255. }
  256. _, err := w.Write([]byte("Content-Type: " + attach.Mime))
  257. return errors.Wrap(err, "WriteMime")
  258. }),
  259. gomail.SetCopyFunc(func(w io.Writer) error {
  260. contBytes, err := base64.StdEncoding.DecodeString(attach.Base64Content)
  261. if err != nil {
  262. return errors.Wrap(err, "base64.StdEncoding.DecodeString")
  263. }
  264. _, err = w.Write(contBytes)
  265. return errors.Wrap(err, "WriteContent")
  266. }),
  267. )
  268. }
  269. errs := make([]error, 0)
  270. for tryTime := 3; tryTime > 0; tryTime-- {
  271. err = gomail.Send(sender, gmsg)
  272. if err != nil {
  273. errs = append(errs, err)
  274. time.Sleep(time.Second * 10)
  275. continue
  276. }
  277. errs = errs[0:0]
  278. break
  279. }
  280. if len(errs) > 0 {
  281. retErr[to] = errors.NewAggregate(errs)
  282. }
  283. }
  284. if len(retErr) > 0 {
  285. return retErr
  286. }
  287. return nil
  288. }
  289. */