go发送smtp邮件时的踩坑记录——authlogin、x509:cannotvalida...

go发送smtp邮件时的踩坑记录——authlogin、x509:cannotvalida...

2023年6月24日发(作者:)

go发送smtp邮件时的踩坑记录——authlogin、x509:cannotvalida。。。最近在⽤go写⼀个⼩⼯具,⼀个⼩功能是⽤smtp发邮件,⽤公司内⽹的邮箱服务器实现踩了不少坑想知道x509: cannot validate certificate for解决的直接看2.2.1,想知道auth login怎么实现看2.2.21 smtp协议基础知识,回顾⼀下smtp协议的基本使⽤1.1 命令⾏通过smtp协议发邮件smtp协议⽹上资料很多,这⾥⽤最简单的⽅法过⼀遍,⽤的是qq邮箱qq邮箱在使⽤smtp协议的时候,⽤的不是qq密码,⽽是⼀个叫授权码的东西,我们去qq邮箱设置——账户⾥找到⽣成授权码他会让你⽤密保⼿机发短信到某个号码,照做即可获得⼀个16位字母的授权码,保存好去⼀个在线加密base64的⽹站,我⽤的是这个把⽤来发邮件的qq邮箱账号和授权码转成base64编码现在打开命令⾏,连接qq的smtp服务器和端⼝,qq的是:25telnet 25要和他打个招呼,后⾯跟着的不⼀定要是smtp,我不是很清楚这个有什么区别,我试着是什么都⾏helo smtp接下来就是验证你的⾝份,我们实验auth login法auth login分两⾏,填⼊刚才转换成base64的账号和授权码,这⾥也可以把账号和auth login放在⼀⾏写,下⼀⾏再写密码响应235 Authentication successful,表⽰登陆成功现在开始配置好发件⼈和收件⼈mail from:<你的发件邮箱>rcpt to:<接收邮箱>输⼊data,开始写邮件内容,写完后⼀个.表⽰邮件结束,返回250 Ok: queued as,邮件就发出去了datasubject:填写邮件主题<空⼀⾏>填写邮件内容...邮件内容.1.2 smtp auth⽅式之前⽤的是auth login⽅式,smtp还有很多其他⽅式,可参考这篇⽂章⽤ehlo来代替helo命令,就可以查询这个邮件服务器⽀持的auth⽅式我在qq邮箱和我公司邮件服务器上尝试ehlo,得到的返回如下

所以qq⽀持auth login和plain两种⽅式,我公司的邮件服务器只⽀持auth login,plain的格式是账号密码

2 如何⽤go发出⼀封auth login的邮件2.1 官⽅是怎么说的官⽅godoc给出了⼀个plain验证⽅式的发邮件代码package mainimport ( "log" "net/smtp")func main() { // Set up authentication information. auth := uth("", "user@", "password", "") // Connect to the server, authenticate, set the sender and recipient, // and send the email all in one step. to := []string{"recipient@"} msg := []byte("To: recipient@" + "Subject: discount Gophers!rn" + "rn" + "This is the email ") err := il(":25", auth, "sender@", to, msg) if err != nil { (err) }}把上⾯的收发件⼈邮箱改好,邮箱服务器的hostname、端⼝改好,我⽤的qq邮箱,如果你⽤别的邮箱,smtp的端⼝号也查⼀下,不⼀定是25密码记得要写授权码运⾏之后邮件就发出去了看⼀下内部代码,smtp包⾥这个SendMail函数,注释是我⾃⼰写的,⼤部分和之前telnet⾛的流程⼀致/* addr: 邮件 smtp 服务器地址 a: 验证对象 from: 发件箱 to: 收件⼈邮箱列表 msg: 发送的邮件信息 */func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { // 检测收发件邮箱地址是否有回车和换⾏ if err := validateLine(from); err != nil { return err } for _, recp := range to { if err := validateLine(recp); err != nil { return err } } // 和邮箱服务器建⽴ tcp 连接 c, err := Dial(addr) if err != nil { return err } defer () // 发送helo信息 if err = (); err != nil { return err } // 如果邮箱服务器⽀持 ssl/tls 加密 if ok, _ := ion("STARTTLS"); ok { config := &{ServerName: Name} // tls 配置 // 测试安全连接 if testHookStartTLS != nil { testHookStartTLS(config) } // 开始 tls 连接 if err = LS(config); err != nil { return err } } // 验证 if a != nil && != nil { // 若邮箱服务器不⽀持 auth,报错 if _, ok := ["AUTH"]; !ok { return ("smtp: server doesn't support AUTH") } // 验证 if err = (a); err != nil { return err } } // 填写发件邮箱 if err = (from); err != nil { return err } // 填写收件邮箱 for _, addr := range to { if err = (addr); err != nil { return err } } // 邮件正⽂ w, err := () if err != nil { return err } _, err = (msg) if err != nil { return err } err = () if err != nil { return err } return ()}2.2 然⽽换成我们公司的邮箱服务器就报错2.2.1 证书错误换上我们公司的邮箱服务器,报错x509: cannot validate certificate for 10.141.72.4 because it doesn't contain any IP SANs⽅法就是要修改代码,配置tls连接为跳过证书验证,我直接把smtp包复制了⼀份,命名为mySmtp/smtp,进⾏修改,修改注释的那⼀⾏就可以,增加InsecureSkipVerify为true的tls配置func SendMail(addr string, a Auth, from string, to []string, msg []byte) error { if err := validateLine(from); err != nil { return err } for _, recp := range to { if err := validateLine(recp); err != nil { return err } } c, err := Dial(addr) if err != nil { return err } defer () if err = (); err != nil { return err } if ok, _ := ion("STARTTLS"); ok { // 跳过证书验证 config := &{ServerName: Name, InsecureSkipVerify: true} if testHookStartTLS != nil { testHookStartTLS(config) } if err = LS(config); err != nil { return err } } if a != nil && != nil { if _, ok := ["AUTH"]; !ok { return ("smtp: server doesn't support AUTH") } if err = (a); err != nil { return err } } if err = (from); err != nil { return err } for _, addr := range to { if err = (addr); err != nil { return err } } w, err := () if err != nil { return err } _, err = (msg) if err != nil { return err } err = () if err != nil { return err } return ()}另外原来smtp包⾥还有,这个⽂件也要⼀并复制到mySmtp包⾥现在main函数⾥⾯smtp的调⽤都变成我们mySmtp包,运⾏之后504 this command is not implemented2.2.2 auth login前⾯说过,smtp的验证⽅式有auth login和plain等,go给出的代码⽤的是plain⽅式验证,⽽我们公司的服务器只⽀持auth login就是说我们还要修改⼀下auth部分的代码,看⼀下原来代码是如何auth的/* ⾝份验证 */func (c *Client) Auth(a Auth) error { // 发送 ehlo if err := (); err != nil { return err } encoding := oding // 获取验证所需信息,mech ⽤于验证的命令,resp 是验证的账号、密码等信息 mech, resp, err := (&ServerInfo{Name, , }) if err != nil { () return err } // base64 编码 resp64 := make([]byte, dLen(len(resp))) (resp64, resp) // 发送验证命令 code, msg64, err := (0, ace(f("AUTH %s %s", mech, resp64))) for err == nil { var msg []byte switch code { // 返回码 334,表⽰期待⽤户继续输⼊信息 case 334: msg, err = String(msg64) // 返回码 235,表⽰登陆成功 case 235: msg = []byte(msg64) // 其他情况,错误 default: err = &{Code: code, Msg: msg64} } // 如果返回码是 334,获取下⼀步验证所需信息 if err == nil { resp, err = (msg, code == 334) } // 如果出错,停⽌连接 if err != nil { // abort the AUTH (501, "*") () break } // 进⾏下⼀步验证 if resp == nil { break } resp64 = make([]byte, dLen(len(resp))) (resp64, resp) code, msg64, err = (0, string(resp64)) } return err}通过代码看出,Auth这个接⼝有两个⽅法,Start和Next,我们构建auth login的Auth对象的时候写好这两个⽅法就可以了为了进⼀步了解,看⼀下plain的Auth对象,这个包⾥还有CRAMMD5的验证⽅法,感兴趣可以⾃⼰看/* 验证服务器基本信息,返回验证所需信息 */func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) { // 如果不是安全连接,也不是本地的服务器,报错,不允许不安全的连接 if ! && !isLocalhost() { return "", nil, ("unencrypted connection") } // 如果服务器信息和 Auth 对象的服务器信息不⼀致,报错 if != { return "", nil, ("wrong host name") } // 验证时需要的账号密码,x00表⽰ resp := []byte(ty + "x00" + me + "x00" + rd) // "auth plain" 命令 return "PLAIN", resp, nil}/* 进⼀步进⾏验证 */func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) { // 如果服务器需要更多验证,报错 if more { return nil, ("unexpected server challenge") } return nil, nil}了解了这两个⽅法,以及smtp是如何调⽤这两个⽅法进⾏验证的,我们就可以写出⾃⼰的⽤于auth login的Auth代码了/* auth login */type loginAuth struct { username, password string host string}/* auth login 验证 */func LoginAuth(username, password, host string) Auth { return &loginAuth{username, password, host}}/* 初步验证服务器信息,输⼊账号 */func (a *loginAuth) Start(server *ServerInfo) (string, []byte, error) { // 如果不是安全连接,也不是本地的服务器,报错,不允许不安全的连接 if ! && !isLocalhost() { return "", nil, ("unencrypted connection") } // 如果服务器信息和 Auth 对象的服务器信息不⼀致,报错 if != { return "", nil, ("wrong host name") } // 验证时需要的账号 resp := []byte(me) // "auth login" 命令 return "LOGIN", resp, nil}/* 进⼀步进⾏验证,输⼊密码 */func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { // 如果服务器需要更多验证,报错 if more { return []byte(rd), nil } return nil, nil}主函数调⽤我们⾃⼰写的Auth和smtp,运⾏,发送成功func main() { hostname := "邮箱IP" auth := uth("发件邮箱", "密码", hostname) // Connect to the server, authenticate, set the sender and recipient, // and send the email all in one step. to := []string{"收件⼈邮箱"} msg := []byte("To: 收件⼈邮箱rn" + "Subject: 测试!rn" + "rn" + "This is the email ") err := il("邮箱IP:SMTP端⼝", auth, "发件邮箱", to, msg) if err != nil { (err) }}

发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1687607878a24240.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信