zoukankan      html  css  js  c++  java
  • golang笔记:net/smtp

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

     
    先用最标准的例子
    
    host := net.JoinHostPort(hostname, port)
    
    auth := smtp.PlainAuth("", username, password, hostname)
    
    to := []string{address}
    
    msg := []byte("To: " + 
            address +
    
            "
    " +
    
            "Subject:" +
    
            title +
    
            "
    " +
    
            "
    " +        
            content +      
            "
    ")            
    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吧!

    
    
  • 相关阅读:
    Centos7安装go.10.1环境
    centos7安装PHP5
    Linux 无文件攻击memfd_create()具体操作步骤
    centos7 '/mnt/hgfs'下共享文件夹不显示问题
    fiddler连接代理手机无法上网问题解决办法
    centos 镜像软件安装包版本低,手动安装过程
    0 upgraded, 0 newly installed, 0 to remove and 112 not upgraded解决方法
    JavaScript高级程序设计(第3版)第七章读书笔记
    JavaScript高级程序设计(第3版)第六章读书笔记
    JavaScript高级程序设计(第3版)第五章读书笔记
  • 原文地址:https://www.cnblogs.com/liumuqiu/p/4846929.html
Copyright © 2011-2022 走看看