diff --git a/models/login.go b/models/login.go index 05ffac2c..73137265 100644 --- a/models/login.go +++ b/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) +}