Browse Source

add smtp authentication

Lunny Xiao 10 years ago
parent
commit
b33f255c40
1 changed files with 125 additions and 4 deletions
  1. 125 4
      models/login.go

+ 125 - 4
models/login.go

@@ -7,6 +7,8 @@ package models
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
+	"net/smtp"
 	"strings"
 	"time"
 
@@ -50,6 +52,22 @@ func (cfg *LDAPConfig) ToDB() ([]byte, error) {
 	return json.Marshal(cfg.Ldapsource)
 }
 
+type SMTPConfig struct {
+	Auth string
+	Host string
+	Post string
+	TLS  bool
+}
+
+// implement
+func (cfg *SMTPConfig) FromDB(bs []byte) error {
+	return json.Unmarshal(bs, cfg)
+}
+
+func (cfg *SMTPConfig) ToDB() ([]byte, error) {
+	return json.Marshal(cfg)
+}
+
 type LoginSource struct {
 	Id                int64
 	Type              int
@@ -69,6 +87,10 @@ func (source *LoginSource) LDAP() *LDAPConfig {
 	return source.Cfg.(*LDAPConfig)
 }
 
+func (source *LoginSource) SMTP() *SMTPConfig {
+	return source.Cfg.(*SMTPConfig)
+}
+
 // for xorm callback
 func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
 	if colName == "type" {
@@ -76,6 +98,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
 		switch ty {
 		case LT_LDAP:
 			source.Cfg = new(LDAPConfig)
+		case LT_SMTP:
+			source.Cfg = new(SMTPConfig)
 		}
 	}
 }
@@ -172,10 +196,19 @@ func LoginUser(uname, passwd string) (*User, error) {
 			}
 
 			for _, source := range sources {
-				u, err := LoginUserLdapSource(nil, u.LoginName, passwd,
-					source.Id, source.Cfg.(*LDAPConfig), true)
-				if err == nil {
-					return u, err
+				if source.Type == LT_LDAP {
+					u, err := LoginUserLdapSource(nil, u.LoginName, passwd,
+						source.Id, source.Cfg.(*LDAPConfig), true)
+					if err == nil {
+						return u, err
+					}
+				} else if source.Type == LT_SMTP {
+					u, err := LoginUserSMTPSource(nil, u.LoginName, passwd,
+						source.Id, source.Cfg.(*SMTPConfig), true)
+
+					if err == nil {
+						return u, err
+					}
 				}
 			}
 
@@ -200,6 +233,8 @@ func LoginUser(uname, passwd string) (*User, error) {
 			return LoginUserLdapSource(u, u.LoginName, passwd,
 				source.Id, source.Cfg.(*LDAPConfig), false)
 		case LT_SMTP:
+			return LoginUserSMTPSource(u, u.LoginName, passwd,
+				source.Id, source.Cfg.(*SMTPConfig), false)
 		}
 		return nil, ErrUnsupportedLoginType
 	}
@@ -232,3 +267,89 @@ func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *L
 
 	return RegisterUser(user)
 }
+
+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", []byte(a.username), nil
+}
+
+func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
+	if more {
+		switch string(fromServer) {
+		case "Username:":
+			return []byte(a.username), nil
+		case "Password:":
+			return []byte(a.password), nil
+		}
+	}
+	return nil, nil
+}
+
+var (
+	smtpAuths = []string{"plain", "login", ""}
+)
+
+func SmtpAuth(addr string, a smtp.Auth) error {
+	c, err := smtp.Dial(addr)
+	if err != nil {
+		return err
+	}
+	defer c.Close()
+
+	if ok, _ := c.Extension("STARTTLS"); ok {
+		if err = c.StartTLS(nil); err != nil {
+			return err
+		}
+	}
+
+	if ok, _ := c.Extension("AUTH"); ok {
+		if err = c.Auth(a); err != nil {
+			return err
+		}
+		return nil
+	} else {
+		return ErrUnsupportedLoginType
+	}
+}
+
+// Query if name/passwd can login against the LDAP direcotry pool
+// Create a local user if success
+// Return the same LoginUserPlain semantic
+func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
+	var auth smtp.Auth
+	if cfg.Auth == "plain" {
+		auth = smtp.PlainAuth("", name, passwd, cfg.Host)
+	} else if cfg.Auth == "login" {
+		auth = LoginAuth(name, passwd)
+	}
+
+	err := SmtpAuth(fmt.Sprintf("%s:%d", cfg.Host, cfg.Post), auth)
+	if err != nil {
+		return nil, err
+	}
+
+	if !autoRegister {
+		return user, nil
+	}
+
+	// fake a local user creation
+	user = &User{
+		LowerName:   strings.ToLower(name),
+		Name:        strings.ToLower(name),
+		LoginType:   LT_SMTP,
+		LoginSource: sourceId,
+		LoginName:   name,
+		IsActive:    true,
+		Passwd:      passwd,
+		Email:       name,
+	}
+
+	return RegisterUser(user)
+}