auth.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // Copyright 2010 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package emailer
  5. import (
  6. "crypto/hmac"
  7. "crypto/md5"
  8. "errors"
  9. "fmt"
  10. )
  11. // Auth is implemented by an SMTP authentication mechanism.
  12. type Auth interface {
  13. // Start begins an authentication with a server.
  14. // It returns the name of the authentication protocol
  15. // and optionally data to include in the initial AUTH message
  16. // sent to the server.
  17. // If it returns a non-nil error, the SMTP client aborts
  18. // the authentication attempt and closes the connection.
  19. Start(server *ServerInfo) (proto string, toServer []byte, err error)
  20. // Next continues the authentication. The server has just sent
  21. // the fromServer data. If more is true, the server expects a
  22. // response, which Next should return as toServer; otherwise
  23. // Next should return toServer == nil.
  24. // If Next returns a non-nil error, the SMTP client aborts
  25. // the authentication attempt and closes the connection.
  26. Next(fromServer []byte, more bool) (toServer []byte, err error)
  27. IsTLS() bool
  28. }
  29. // ServerInfo records information about an SMTP server.
  30. type ServerInfo struct {
  31. Name string // SMTP server name
  32. TLS bool // using TLS, with valid certificate for Name
  33. Auth []string // advertised authentication mechanisms
  34. }
  35. type plainAuth struct {
  36. identity, username, password string
  37. host string
  38. tls bool
  39. }
  40. func (s *plainAuth) IsTLS() bool {
  41. return s.tls
  42. }
  43. // PlainAuth returns an Auth that implements the PLAIN authentication
  44. // mechanism as defined in RFC 4616. The returned Auth uses the given
  45. // username and password to authenticate to host and act as identity.
  46. // Usually identity should be the empty string, to act as username.
  47. //
  48. // PlainAuth will only send the credentials if the connection is using TLS
  49. // or is connected to localhost. Otherwise authentication will fail with an
  50. // error, without sending the credentials.
  51. func PlainAuth(identity, username, password, host string, tls bool) Auth {
  52. return &plainAuth{identity, username, password, host, tls}
  53. }
  54. func isLocalhost(name string) bool {
  55. return name == "localhost" || name == "127.0.0.1" || name == "::1"
  56. }
  57. func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
  58. // Must have TLS, or else localhost server.
  59. // Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
  60. // In particular, it doesn't matter if the server advertises PLAIN auth.
  61. // That might just be the attacker saying
  62. // "it's ok, you can trust me with your password."
  63. if !server.TLS && !isLocalhost(server.Name) {
  64. return "", nil, errors.New("unencrypted connection")
  65. }
  66. if server.Name != a.host {
  67. return "", nil, errors.New("wrong host name")
  68. }
  69. resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
  70. return "PLAIN", resp, nil
  71. }
  72. func (a *plainAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  73. if more {
  74. // We've already sent everything.
  75. return nil, errors.New("unexpected server challenge")
  76. }
  77. return nil, nil
  78. }
  79. type cramMD5Auth struct {
  80. username, secret string
  81. tls bool
  82. }
  83. // CRAMMD5Auth returns an Auth that implements the CRAM-MD5 authentication
  84. // mechanism as defined in RFC 2195.
  85. // The returned Auth uses the given username and secret to authenticate
  86. // to the server using the challenge-response mechanism.
  87. func CRAMMD5Auth(username, secret string, tls bool) Auth {
  88. return &cramMD5Auth{username, secret, tls}
  89. }
  90. func (s *cramMD5Auth) IsTLS() bool {
  91. return s.tls
  92. }
  93. func (a *cramMD5Auth) Start(server *ServerInfo) (string, []byte, error) {
  94. return "CRAM-MD5", nil, nil
  95. }
  96. func (a *cramMD5Auth) Next(fromServer []byte, more bool) ([]byte, error) {
  97. if more {
  98. d := hmac.New(md5.New, []byte(a.secret))
  99. d.Write(fromServer)
  100. s := make([]byte, 0, d.Size())
  101. return []byte(fmt.Sprintf("%s %x", a.username, d.Sum(s))), nil
  102. }
  103. return nil, nil
  104. }