分享
  1. 首页
  2. 文章

golang笔记:net/smtp

liumuqiu · · 7603 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

跟go语言的net/smtp斗争了一天,记录下历程。

先用最标准的例子
host := net.JoinHostPort(hostname, port)
auth := smtp.PlainAuth("", username, password, hostname)
to := []string{address}
msg := []byte("To: " + 
 address +
 "\r\n" +
 "Subject:" +
 title +
 "\r\n" +
 "\r\n" + 
 content + 
 "\r\n") 
err := smtp.SendMail(host, auth, from, to, msg)
程序持续报一个 unencrypted connection 的错误。原来新版本的smtp为了防止密码以明文传输,强制以SSL连接发送邮件。但我手上的服务器没有SSL连接,只好去库里看在哪儿做的判断,找到auth.go里面func Start()中的这样一段话
if !server.TLS {
 advertised := false
 for _, mechanism := range server.Auth {
 if mechanism == "PLAIN" {
 advertised = true
 break
 }
 }
 if !advertised {
 return "", nil, errors.New("unencrypted connection")
 }
}
看样子判断是在这里进行的了。在网上找到一个伪装TLS链接的方法。
首先,在代码里加上
/*use unSSL to link mail server*/
type unencryptedAuth struct {
 smtp.Auth
}
func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
 s := *server
 s.TLS = true 
 login, resp, th := a.Auth.Start(&s)
 return "LOGIN", resp, th
}
将TLS的值设为true, 发邮件部分这样写
auth := unencryptedAuth {
 smtp.PlainAuth(
 "",
 username,
 password,
 hostname,
 )
}
err := smtp.SendMail(host, auth, from, to, msg)
这样链接成立了,报的错误变成 unrecognized authentication type. 查到func Start() 的返回值为
 return "PLAIN", resp, nil
原来这里强制以plain登陆。参考前人的方法修改思路,重写Start方法
type loginAuth struct {
 username, password string
}
func LoginAuth(username, password string) smtp.Auth {
 return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { 
 return "LOGIN", nil, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
 command := string(fromServer)
 command = strings.TrimSpace(command)
 command = strings.TrimSuffix(command, ":")
 command = strings.ToLower(command)
 if more {
 if (command == "username") {
 return []byte(fmt.Sprintf("%s", a.username)), nil
 } else if (command == "password") {
 return []byte(fmt.Sprintf("%s", a.password)), nil
 } else {
 // We've already sent everything.
 return nil, fmt.Errorf("unexpected server challenge: %s", command)
 }
 }
 return nil, nil
}
Login的认证方式协议和Plain不同,所以Next方法也重写了,不然报那个unexpected server challenge的错误,这样就能顺利地使用用户名和密码认证,发邮件的认证部分这样写:
 auth := LoginAuth("username, password)
如此一来就可以成功发送邮件了。
但是当我换用另一台邮件服务器时,又出现了certificate signed by unknown authority
部署到服务器上时,错误显示为cannot validate certificate for 10.11.64.80 because it doesn't contain any IP SANS
总之都是类似于认证的问题。这两台服务器的区别是第一台使用465端口,即smtps,而第二台使用25端口。
查看smtp.go发现func SendMail()中有这样一段
if ok, _ := c.Extension("STARTTLS"); ok {
config := &tls.Config{ServerName: c.serverName}
 if testHookStartTLS != nil {
 testHookStartTLS(config)
 }
 if err = c.StartTLS(config); err != nil {
 return err
 }
}
我干脆把SendMail方法拷出来,去掉这一段判断,同时smtp里面涉及到的func和struct都拷出来,写了一个新的.go,在发邮件的时候直接使用这个新的SendMail。部分原有的公共方法和结构不用拷出,直接以smtp.调用,如此一来就能直接用端口25的那台服务器发邮件了。
虽然邮件发送成功,但是查看日志里总输出一个错误250 Mail OK queued as XXXX,看着很不爽,但这输出又不像错误。按照telnet hostname port后的操作对照SendMail的执行过程。发现在发送DATA指令之后,会收到一个回复码354,接收输入邮件内容,以句号回车结尾后,会再收到一个250的回复。在代码中,发送了DATA,收到354,接着发送邮件内容,代码并未接收这个250。最后发送QUIT,这里收到的是上一个回复码250,和QUIT的正常回复码221作比较,程序就会返回error。我也不知道哪个函数可以只接收回复,简单起见,干脆在Quit函数里发了两遍QUIT,判断第一个返回250,第二个返回221,终于不再报错。
研究完这个函数,对smtp就从一无所知到相当了解了。另外,要从根本上解决问题,还是升级为SSL吧!


有疑问加站长微信联系(非本文作者)

本文来自:博客园

感谢作者:liumuqiu

查看原文:golang笔记:net/smtp

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
7603 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏