<commit_before>package libkb

import (
	"fmt"
	"os"
	"sync"
	"time"

	jsonw "github.com/keybase/go-jsonw"
)

type Session struct {
	Contextified
	file     *JsonFile
	token    string
	csrf     string
	inFile   bool
	loaded   bool
	checked  bool
	valid    bool
	uid      *UID
	username *string
	mtime    int64
	sync.RWMutex
}

func newSession(g *GlobalContext) *Session {
	return &Session{Contextified: Contextified{g}}
}

// NewSessionThin creates a minimal (thin) session of just the uid and username.
// Clients of the daemon that use the session protocol need this.
func NewSessionThin(uid UID, username string, token string) *Session {
	// XXX should this set valid to true?  daemon won't return a
	// session unless valid is true, so...
	return &Session{uid: &uid, username: &username, token: token, valid: true}
}

func (s *Session) IsLoggedIn() bool {
	s.RLock()
	defer s.RUnlock()
	return s.valid
}

func (s *Session) GetUsername() *string {
	s.RLock()
	defer s.RUnlock()
	return s.username
}

func (s *Session) GetUID() *UID {
	s.RLock()
	defer s.RUnlock()
	return s.uid
}

func (s *Session) GetToken() string {
	s.RLock()
	defer s.RUnlock()
	return s.token
}

func (s *Session) GetCsrf() string {
	s.RLock()
	defer s.RUnlock()
	return s.csrf
}

func (s *Session) SetLoggedIn(sessionID, csrfToken, username string, uid UID) {
	s.Lock()
	s.valid = true
	s.uid = &uid
	s.username = &username
	s.token = sessionID
	s.GetDictionary().SetKey("session", jsonw.NewString(sessionID))
	s.Unlock()

	s.SetCsrf(csrfToken)
	s.SetDirty()
}

func (s *Session) SetDirty() {
	s.Lock()
	defer s.Unlock()
	s.file.dirty = true
	s.GetDictionary().SetKey("mtime", jsonw.NewInt64(time.Now().Unix()))
}

func (s *Session) SetCsrf(t string) {
	s.Lock()
	s.csrf = t
	if s.file == nil {
		s.Unlock()
		return
	}
	s.GetDictionary().SetKey("csrf", jsonw.NewString(t))
	s.Unlock()
	s.SetDirty()
}

func (s *Session) isConfigLoggedIn() bool {
	reader := s.G().Env.GetConfig()
	return reader.GetUsername() != "" && reader.GetDeviceID() != nil && reader.GetUID() != nil
}

// The session file can be out of sync with the config file, particularly when
// switching between the node and go clients.
func (s *Session) nukeSessionFileIfOutOfSync() error {
	sessionFile := s.G().Env.GetSessionFilename()
	// Use stat to check existence.
	_, statErr := os.Lstat(sessionFile)
	if statErr == nil && !s.isConfigLoggedIn() {
		s.G().Log.Warning("Session file found but user is not logged in. Deleting session file.")
		return os.Remove(sessionFile)
	}
	return nil
}

func (s *Session) Load() error {
	s.RLock()
	s.G().Log.Debug("+ Loading session")
	if s.loaded {
		s.G().Log.Debug("- Skipped; already loaded")
		s.RUnlock()
		return nil
	}
	s.RUnlock()

	s.Lock()
	defer s.Unlock()

	err := s.nukeSessionFileIfOutOfSync()
	if err != nil {
		return err
	}

	s.file = NewJsonFile(s.G().Env.GetSessionFilename(), "session")
	err = s.file.Load(false)
	s.loaded = true

	if err != nil {
		s.G().Log.Error("Failed to load session file")
		return err
	}

	if s.file.Exists() {
		var tmp error
		var token, csrf string
		ok := true
		s.file.jw.AtKey("session").GetStringVoid(&token, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'session' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		s.file.jw.AtKey("csrf").GetStringVoid(&csrf, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'csrf' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		mtime, _ := s.file.jw.AtKey("mtime").GetInt64()
		if ok {
			s.token = token
			s.csrf = csrf
			s.inFile = true
			s.mtime = mtime
		}
	}
	s.G().Log.Debug("- Loaded session")
	return nil
}

func (s *Session) GetDictionary() *jsonw.Wrapper {
	return s.file.jw
}

func (s *Session) Write() error {
	s.Lock()
	defer s.Unlock()
	return s.file.MaybeSave(true, 0)
}

func (s *Session) IsRecent() bool {
	s.RLock()
	defer s.RUnlock()
	if s.mtime == 0 {
		return false
	}
	t := time.Unix(s.mtime, 0)
	return time.Since(t) < time.Hour
}

func (s *Session) Check() error {
	s.RLock()
	s.G().Log.Debug("+ Checking session")
	if s.checked {
		s.G().Log.Debug("- already checked, short-circuting")
		s.RUnlock()
		return nil
	}
	s.RUnlock()
	s.Lock()
	s.checked = true
	s.Unlock()

	res, err := s.G().API.Get(ApiArg{
		Endpoint:    "sesscheck",
		NeedSession: true,
		AppStatus:   []string{"OK", "BAD_SESSION"},
	})

	if err != nil {
		return err
	}
	if res.AppStatus == "OK" {
		s.G().Log.Debug("| Stored session checked out")
		var err error
		var uid UID
		var username, csrf string
		GetUidVoid(res.Body.AtKey("logged_in_uid"), &uid, &err)
		res.Body.AtKey("username").GetStringVoid(&username, &err)
		res.Body.AtKey("csrf_token").GetStringVoid(&csrf, &err)
		if err != nil {
			err = fmt.Errorf("Server replied with unrecognized response: %s", err.Error())
			return err
		} else {
			s.Lock()
			s.valid = true
			s.uid = &uid
			s.username = &username
			s.Unlock()
			if !s.IsRecent() {
				s.SetCsrf(csrf)
			}
		}
	} else {
		s.G().Log.Notice("Stored session expired")
		s.Lock()
		s.valid = false
		s.Unlock()
	}

	s.G().Log.Debug("- Checked session")
	return nil
}

func (s *Session) HasSessionToken() bool {
	s.RLock()
	defer s.RUnlock()
	return len(s.token) > 0
}

func (s *Session) IsValid() bool {
	s.RLock()
	defer s.RUnlock()
	return s.valid
}

func (s *Session) postLogout() error {
	_, err := s.G().API.Post(ApiArg{
		Endpoint:    "logout",
		NeedSession: true,
	})
	if err == nil {
		s.Lock()
		s.valid = false
		s.checked = false
		s.token = ""
		s.csrf = ""
		s.Unlock()
	}
	return err
}

func (s *Session) Logout() error {
	err := s.Load()
	var e2 error
	if err == nil && s.HasSessionToken() {
		e2 = s.postLogout()
		s.Lock()
		if e3 := s.file.Nuke(); e3 != nil {
			s.inFile = false
			s.G().Log.Warning("Failed to remove session file: %s", e3.Error())
		}
		s.Unlock()
	}
	if err == nil && e2 != nil {
		err = e2
	}
	return err
}

func (s *Session) loadAndCheck() (bool, error) {
	err := s.Load()
	if err != nil {
		return false, err
	}
	if s.HasSessionToken() {
		err = s.Check()
	}
	return s.IsValid(), err
}
<commit_msg>Closed #297 (only set session to checked if sesscheck api call succeeds)<commit_after>package libkb

import (
	"fmt"
	"os"
	"sync"
	"time"

	jsonw "github.com/keybase/go-jsonw"
)

type Session struct {
	Contextified
	file     *JsonFile
	token    string
	csrf     string
	inFile   bool
	loaded   bool
	checked  bool
	valid    bool
	uid      *UID
	username *string
	mtime    int64
	sync.RWMutex
}

func newSession(g *GlobalContext) *Session {
	return &Session{Contextified: Contextified{g}}
}

// NewSessionThin creates a minimal (thin) session of just the uid and username.
// Clients of the daemon that use the session protocol need this.
func NewSessionThin(uid UID, username string, token string) *Session {
	// XXX should this set valid to true?  daemon won't return a
	// session unless valid is true, so...
	return &Session{uid: &uid, username: &username, token: token, valid: true}
}

func (s *Session) IsLoggedIn() bool {
	s.RLock()
	defer s.RUnlock()
	return s.valid
}

func (s *Session) GetUsername() *string {
	s.RLock()
	defer s.RUnlock()
	return s.username
}

func (s *Session) GetUID() *UID {
	s.RLock()
	defer s.RUnlock()
	return s.uid
}

func (s *Session) GetToken() string {
	s.RLock()
	defer s.RUnlock()
	return s.token
}

func (s *Session) GetCsrf() string {
	s.RLock()
	defer s.RUnlock()
	return s.csrf
}

func (s *Session) SetLoggedIn(sessionID, csrfToken, username string, uid UID) {
	s.Lock()
	s.valid = true
	s.uid = &uid
	s.username = &username
	s.token = sessionID
	s.GetDictionary().SetKey("session", jsonw.NewString(sessionID))
	s.Unlock()

	s.SetCsrf(csrfToken)
	s.SetDirty()
}

func (s *Session) SetDirty() {
	s.Lock()
	defer s.Unlock()
	s.file.dirty = true
	s.GetDictionary().SetKey("mtime", jsonw.NewInt64(time.Now().Unix()))
}

func (s *Session) SetCsrf(t string) {
	s.Lock()
	s.csrf = t
	if s.file == nil {
		s.Unlock()
		return
	}
	s.GetDictionary().SetKey("csrf", jsonw.NewString(t))
	s.Unlock()
	s.SetDirty()
}

func (s *Session) isConfigLoggedIn() bool {
	reader := s.G().Env.GetConfig()
	return reader.GetUsername() != "" && reader.GetDeviceID() != nil && reader.GetUID() != nil
}

// The session file can be out of sync with the config file, particularly when
// switching between the node and go clients.
func (s *Session) nukeSessionFileIfOutOfSync() error {
	sessionFile := s.G().Env.GetSessionFilename()
	// Use stat to check existence.
	_, statErr := os.Lstat(sessionFile)
	if statErr == nil && !s.isConfigLoggedIn() {
		s.G().Log.Warning("Session file found but user is not logged in. Deleting session file.")
		return os.Remove(sessionFile)
	}
	return nil
}

func (s *Session) Load() error {
	s.RLock()
	s.G().Log.Debug("+ Loading session")
	if s.loaded {
		s.G().Log.Debug("- Skipped; already loaded")
		s.RUnlock()
		return nil
	}
	s.RUnlock()

	s.Lock()
	defer s.Unlock()

	err := s.nukeSessionFileIfOutOfSync()
	if err != nil {
		return err
	}

	s.file = NewJsonFile(s.G().Env.GetSessionFilename(), "session")
	err = s.file.Load(false)
	s.loaded = true

	if err != nil {
		s.G().Log.Error("Failed to load session file")
		return err
	}

	if s.file.Exists() {
		var tmp error
		var token, csrf string
		ok := true
		s.file.jw.AtKey("session").GetStringVoid(&token, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'session' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		s.file.jw.AtKey("csrf").GetStringVoid(&csrf, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'csrf' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		mtime, _ := s.file.jw.AtKey("mtime").GetInt64()
		if ok {
			s.token = token
			s.csrf = csrf
			s.inFile = true
			s.mtime = mtime
		}
	}
	s.G().Log.Debug("- Loaded session")
	return nil
}

func (s *Session) GetDictionary() *jsonw.Wrapper {
	return s.file.jw
}

func (s *Session) Write() error {
	s.Lock()
	defer s.Unlock()
	return s.file.MaybeSave(true, 0)
}

func (s *Session) IsRecent() bool {
	s.RLock()
	defer s.RUnlock()
	if s.mtime == 0 {
		return false
	}
	t := time.Unix(s.mtime, 0)
	return time.Since(t) < time.Hour
}

func (s *Session) Check() error {
	s.RLock()
	s.G().Log.Debug("+ Checking session")
	if s.checked {
		s.G().Log.Debug("- already checked, short-circuting")
		s.RUnlock()
		return nil
	}
	s.RUnlock()

	res, err := s.G().API.Get(ApiArg{
		Endpoint:    "sesscheck",
		NeedSession: true,
		AppStatus:   []string{"OK", "BAD_SESSION"},
	})

	if err != nil {
		return err
	}

	s.Lock()
	s.checked = true
	s.Unlock()

	if res.AppStatus == "OK" {
		s.G().Log.Debug("| Stored session checked out")
		var err error
		var uid UID
		var username, csrf string
		GetUidVoid(res.Body.AtKey("logged_in_uid"), &uid, &err)
		res.Body.AtKey("username").GetStringVoid(&username, &err)
		res.Body.AtKey("csrf_token").GetStringVoid(&csrf, &err)
		if err != nil {
			err = fmt.Errorf("Server replied with unrecognized response: %s", err.Error())
			return err
		} else {
			s.Lock()
			s.valid = true
			s.uid = &uid
			s.username = &username
			s.Unlock()
			if !s.IsRecent() {
				s.SetCsrf(csrf)
			}
		}
	} else {
		s.G().Log.Notice("Stored session expired")
		s.Lock()
		s.valid = false
		s.Unlock()
	}

	s.G().Log.Debug("- Checked session")
	return nil
}

func (s *Session) HasSessionToken() bool {
	s.RLock()
	defer s.RUnlock()
	return len(s.token) > 0
}

func (s *Session) IsValid() bool {
	s.RLock()
	defer s.RUnlock()
	return s.valid
}

func (s *Session) postLogout() error {
	_, err := s.G().API.Post(ApiArg{
		Endpoint:    "logout",
		NeedSession: true,
	})
	if err == nil {
		s.Lock()
		s.valid = false
		s.checked = false
		s.token = ""
		s.csrf = ""
		s.Unlock()
	}
	return err
}

func (s *Session) Logout() error {
	err := s.Load()
	var e2 error
	if err == nil && s.HasSessionToken() {
		e2 = s.postLogout()
		s.Lock()
		if e3 := s.file.Nuke(); e3 != nil {
			s.inFile = false
			s.G().Log.Warning("Failed to remove session file: %s", e3.Error())
		}
		s.Unlock()
	}
	if err == nil && e2 != nil {
		err = e2
	}
	return err
}

func (s *Session) loadAndCheck() (bool, error) {
	err := s.Load()
	if err != nil {
		return false, err
	}
	if s.HasSessionToken() {
		err = s.Check()
	}
	return s.IsValid(), err
}
<|endoftext|>


<commit_before>package libkb

import (
	"fmt"
	"os"
	"sync"
	"time"

	jsonw "github.com/keybase/go-jsonw"
)

type SessionReader interface {
	APIArgs() (token, csrf string)
}

type Session struct {
	Contextified
	file     *JsonFile
	token    string
	csrf     string
	inFile   bool
	loaded   bool
	checked  bool
	valid    bool
	uid      *UID
	username *string
	mtime    int64
	sync.RWMutex
}

func newSession(g *GlobalContext) *Session {
	return &Session{Contextified: Contextified{g}}
}

// NewSessionThin creates a minimal (thin) session of just the uid and username.
// Clients of the daemon that use the session protocol need this.
func NewSessionThin(uid UID, username string, token string) *Session {
	// XXX should this set valid to true?  daemon won't return a
	// session unless valid is true, so...
	return &Session{uid: &uid, username: &username, token: token, valid: true}
}

func (s *Session) IsLoggedIn() bool {
	s.RLock()
	defer s.RUnlock()
	return s.valid
}

func (s *Session) GetUsername() *string {
	s.RLock()
	defer s.RUnlock()
	return s.username
}

func (s *Session) GetUID() *UID {
	s.RLock()
	defer s.RUnlock()
	return s.uid
}

func (s *Session) GetToken() string {
	s.RLock()
	defer s.RUnlock()
	return s.token
}

func (s *Session) GetCsrf() string {
	s.RLock()
	defer s.RUnlock()
	return s.csrf
}

func (s *Session) APIArgs() (token, csrf string) {
	s.RLock()
	defer s.RUnlock()
	return s.token, s.csrf
}

func (s *Session) SetUsername(username string) {
	s.Lock()
	s.username = &username
	s.Unlock()
}

func (s *Session) SetLoggedIn(sessionID, csrfToken, username string, uid UID) {
	s.Lock()
	s.valid = true
	s.uid = &uid
	s.username = &username
	s.token = sessionID
	if s.file == nil {
		G.Log.Warning("s.file == nil")
		s.Unlock()
		s.Load()
		s.Lock()
	}
	if s.GetDictionary() == nil {
		G.Log.Warning("s.GetDict() == nil")
	}
	s.GetDictionary().SetKey("session", jsonw.NewString(sessionID))
	s.Unlock()

	s.SetCsrf(csrfToken)
	s.SetDirty()
}

func (s *Session) SetDirty() {
	s.Lock()
	defer s.Unlock()
	s.file.dirty = true
	s.GetDictionary().SetKey("mtime", jsonw.NewInt64(time.Now().Unix()))
}

func (s *Session) SetCsrf(t string) {
	s.Lock()
	s.csrf = t
	if s.file == nil {
		s.Unlock()
		return
	}
	s.GetDictionary().SetKey("csrf", jsonw.NewString(t))
	s.Unlock()
	s.SetDirty()
}

func (s *Session) isConfigLoggedIn() bool {
	reader := s.G().Env.GetConfig()
	return reader.GetUsername() != "" && reader.GetDeviceID() != nil && reader.GetUID() != nil
}

// The session file can be out of sync with the config file, particularly when
// switching between the node and go clients.
func (s *Session) nukeSessionFileIfOutOfSync() error {
	sessionFile := s.G().Env.GetSessionFilename()
	// Use stat to check existence.
	_, statErr := os.Lstat(sessionFile)
	if statErr == nil && !s.isConfigLoggedIn() {
		s.G().Log.Warning("Session file found but user is not logged in. Deleting session file.")
		return os.Remove(sessionFile)
	}
	return nil
}

func (s *Session) Load() error {
	s.RLock()
	s.G().Log.Debug("+ Loading session")
	if s.loaded {
		s.G().Log.Debug("- Skipped; already loaded")
		s.RUnlock()
		return nil
	}
	s.RUnlock()

	s.Lock()
	defer s.Unlock()

	err := s.nukeSessionFileIfOutOfSync()
	if err != nil {
		return err
	}

	s.file = NewJsonFile(s.G().Env.GetSessionFilename(), "session")
	err = s.file.Load(false)
	s.loaded = true

	if err != nil {
		s.G().Log.Error("Failed to load session file")
		return err
	}

	if s.file.Exists() {
		var tmp error
		var token, csrf string
		ok := true
		s.file.jw.AtKey("session").GetStringVoid(&token, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'session' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		s.file.jw.AtKey("csrf").GetStringVoid(&csrf, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'csrf' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		mtime, _ := s.file.jw.AtKey("mtime").GetInt64()
		if ok {
			s.token = token
			s.csrf = csrf
			s.inFile = true
			s.mtime = mtime
		}
	}
	s.G().Log.Debug("- Loaded session")
	return nil
}

func (s *Session) GetDictionary() *jsonw.Wrapper {
	return s.file.jw
}

func (s *Session) Write() error {
	s.Lock()
	defer s.Unlock()
	return s.file.MaybeSave(true, 0)
}

func (s *Session) IsRecent() bool {
	s.RLock()
	defer s.RUnlock()
	if s.mtime == 0 {
		return false
	}
	t := time.Unix(s.mtime, 0)
	return time.Since(t) < time.Hour
}

func (s *Session) Check() error {
	s.RLock()
	s.G().Log.Debug("+ Checking session")
	if s.checked {
		s.G().Log.Debug("- already checked, short-circuting")
		s.RUnlock()
		return nil
	}
	s.RUnlock()

	res, err := s.G().API.Get(ApiArg{
		SessionR:    s,
		Endpoint:    "sesscheck",
		NeedSession: true,
		AppStatus:   []string{"OK", "BAD_SESSION"},
	})

	if err != nil {
		return err
	}

	s.Lock()
	s.checked = true
	s.Unlock()

	if res.AppStatus == "OK" {
		s.G().Log.Debug("| Stored session checked out")
		var err error
		var uid UID
		var username, csrf string
		GetUidVoid(res.Body.AtKey("logged_in_uid"), &uid, &err)
		res.Body.AtKey("username").GetStringVoid(&username, &err)
		res.Body.AtKey("csrf_token").GetStringVoid(&csrf, &err)
		if err != nil {
			err = fmt.Errorf("Server replied with unrecognized response: %s", err.Error())
			return err
		}
		s.Lock()
		s.valid = true
		s.uid = &uid
		s.username = &username
		s.Unlock()
		if !s.IsRecent() {
			s.SetCsrf(csrf)
		}
	} else {
		s.G().Log.Notice("Stored session expired")
		s.Lock()
		s.valid = false
		s.Unlock()
	}

	s.G().Log.Debug("- Checked session")
	return nil
}

func (s *Session) HasSessionToken() bool {
	s.RLock()
	defer s.RUnlock()
	return len(s.token) > 0
}

func (s *Session) IsValid() bool {
	s.RLock()
	defer s.RUnlock()
	return s.valid
}

func (s *Session) postLogout() error {

	_, err := s.G().API.Post(ApiArg{
		SessionR:    s,
		Endpoint:    "logout",
		NeedSession: true,
	})
	if err == nil {
		s.Lock()
		s.valid = false
		s.checked = false
		s.token = ""
		s.csrf = ""
		s.Unlock()
	}
	return err
}

func (s *Session) Logout() error {
	err := s.Load()
	var e2 error
	if err == nil && s.HasSessionToken() {
		e2 = s.postLogout()
		s.Lock()
		if e3 := s.file.Nuke(); e3 != nil {
			s.inFile = false
			s.G().Log.Warning("Failed to remove session file: %s", e3.Error())
		}
		s.Unlock()
	}
	if err == nil && e2 != nil {
		err = e2
	}
	return err
}

func (s *Session) loadAndCheck() (bool, error) {
	err := s.Load()
	if err != nil {
		return false, err
	}
	if s.HasSessionToken() {
		err = s.Check()
	}
	return s.IsValid(), err
}
<commit_msg>removed mutex<commit_after>package libkb

import (
	"fmt"
	"os"
	"time"

	jsonw "github.com/keybase/go-jsonw"
)

type SessionReader interface {
	APIArgs() (token, csrf string)
}

type Session struct {
	Contextified
	file     *JsonFile
	token    string
	csrf     string
	inFile   bool
	loaded   bool
	checked  bool
	valid    bool
	uid      *UID
	username *string
	mtime    int64
}

func newSession(g *GlobalContext) *Session {
	return &Session{Contextified: Contextified{g}}
}

// NewSessionThin creates a minimal (thin) session of just the uid and username.
// Clients of the daemon that use the session protocol need this.
func NewSessionThin(uid UID, username string, token string) *Session {
	// XXX should this set valid to true?  daemon won't return a
	// session unless valid is true, so...
	return &Session{uid: &uid, username: &username, token: token, valid: true}
}

func (s *Session) IsLoggedIn() bool {
	return s.valid
}

func (s *Session) GetUsername() *string {
	return s.username
}

func (s *Session) GetUID() *UID {
	return s.uid
}

func (s *Session) GetToken() string {
	return s.token
}

func (s *Session) GetCsrf() string {
	return s.csrf
}

func (s *Session) APIArgs() (token, csrf string) {
	return s.token, s.csrf
}

func (s *Session) SetUsername(username string) {
	s.username = &username
}

func (s *Session) SetLoggedIn(sessionID, csrfToken, username string, uid UID) {
	s.valid = true
	s.uid = &uid
	s.username = &username
	s.token = sessionID
	if s.file == nil {
		G.Log.Warning("s.file == nil")
		s.Load()
	}
	if s.GetDictionary() == nil {
		G.Log.Warning("s.GetDict() == nil")
	}
	s.GetDictionary().SetKey("session", jsonw.NewString(sessionID))

	s.SetCsrf(csrfToken)
	s.SetDirty()
}

func (s *Session) SetDirty() {
	s.file.dirty = true
	s.GetDictionary().SetKey("mtime", jsonw.NewInt64(time.Now().Unix()))
}

func (s *Session) SetCsrf(t string) {
	s.csrf = t
	if s.file == nil {
		return
	}
	s.GetDictionary().SetKey("csrf", jsonw.NewString(t))
	s.SetDirty()
}

func (s *Session) isConfigLoggedIn() bool {
	reader := s.G().Env.GetConfig()
	return reader.GetUsername() != "" && reader.GetDeviceID() != nil && reader.GetUID() != nil
}

// The session file can be out of sync with the config file, particularly when
// switching between the node and go clients.
func (s *Session) nukeSessionFileIfOutOfSync() error {
	sessionFile := s.G().Env.GetSessionFilename()
	// Use stat to check existence.
	_, statErr := os.Lstat(sessionFile)
	if statErr == nil && !s.isConfigLoggedIn() {
		s.G().Log.Warning("Session file found but user is not logged in. Deleting session file.")
		return os.Remove(sessionFile)
	}
	return nil
}

func (s *Session) Load() error {
	s.G().Log.Debug("+ Loading session")
	if s.loaded {
		s.G().Log.Debug("- Skipped; already loaded")
		return nil
	}

	err := s.nukeSessionFileIfOutOfSync()
	if err != nil {
		return err
	}

	s.file = NewJsonFile(s.G().Env.GetSessionFilename(), "session")
	err = s.file.Load(false)
	s.loaded = true

	if err != nil {
		s.G().Log.Error("Failed to load session file")
		return err
	}

	if s.file.Exists() {
		var tmp error
		var token, csrf string
		ok := true
		s.file.jw.AtKey("session").GetStringVoid(&token, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'session' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		s.file.jw.AtKey("csrf").GetStringVoid(&csrf, &tmp)
		if tmp != nil {
			s.G().Log.Warning("Bad 'csrf' value in session file %s: %s",
				s.file.filename, tmp.Error())
			ok = false
		}
		mtime, _ := s.file.jw.AtKey("mtime").GetInt64()
		if ok {
			s.token = token
			s.csrf = csrf
			s.inFile = true
			s.mtime = mtime
		}
	}
	s.G().Log.Debug("- Loaded session")
	return nil
}

func (s *Session) GetDictionary() *jsonw.Wrapper {
	return s.file.jw
}

func (s *Session) Write() error {
	return s.file.MaybeSave(true, 0)
}

func (s *Session) IsRecent() bool {
	if s.mtime == 0 {
		return false
	}
	t := time.Unix(s.mtime, 0)
	return time.Since(t) < time.Hour
}

func (s *Session) Check() error {
	s.G().Log.Debug("+ Checking session")
	if s.checked {
		s.G().Log.Debug("- already checked, short-circuting")
		return nil
	}

	res, err := s.G().API.Get(ApiArg{
		SessionR:    s,
		Endpoint:    "sesscheck",
		NeedSession: true,
		AppStatus:   []string{"OK", "BAD_SESSION"},
	})

	if err != nil {
		return err
	}

	s.checked = true

	if res.AppStatus == "OK" {
		s.G().Log.Debug("| Stored session checked out")
		var err error
		var uid UID
		var username, csrf string
		GetUidVoid(res.Body.AtKey("logged_in_uid"), &uid, &err)
		res.Body.AtKey("username").GetStringVoid(&username, &err)
		res.Body.AtKey("csrf_token").GetStringVoid(&csrf, &err)
		if err != nil {
			err = fmt.Errorf("Server replied with unrecognized response: %s", err.Error())
			return err
		}
		s.valid = true
		s.uid = &uid
		s.username = &username
		if !s.IsRecent() {
			s.SetCsrf(csrf)
		}
	} else {
		s.G().Log.Notice("Stored session expired")
		s.valid = false
	}

	s.G().Log.Debug("- Checked session")
	return nil
}

func (s *Session) HasSessionToken() bool {
	return len(s.token) > 0
}

func (s *Session) IsValid() bool {
	return s.valid
}

func (s *Session) postLogout() error {

	_, err := s.G().API.Post(ApiArg{
		SessionR:    s,
		Endpoint:    "logout",
		NeedSession: true,
	})
	if err == nil {
		s.valid = false
		s.checked = false
		s.token = ""
		s.csrf = ""
	}
	return err
}

func (s *Session) Logout() error {
	err := s.Load()
	var e2 error
	if err == nil && s.HasSessionToken() {
		e2 = s.postLogout()
		if e3 := s.file.Nuke(); e3 != nil {
			s.inFile = false
			s.G().Log.Warning("Failed to remove session file: %s", e3.Error())
		}
	}
	if err == nil && e2 != nil {
		err = e2
	}
	return err
}

func (s *Session) loadAndCheck() (bool, error) {
	err := s.Load()
	if err != nil {
		return false, err
	}
	if s.HasSessionToken() {
		err = s.Check()
	}
	return s.IsValid(), err
}
<|endoftext|>


<commit_before>package activity

import (
	"fmt"
	"reflect"

	"github.com/aws/aws-sdk-go/service/swf"
)

type ActivityHandlerFunc func(activityTask *swf.PollForActivityTaskOutput, input interface{}) (interface{}, error)

type ActivityHandler struct {
	Activity    string
	HandlerFunc ActivityHandlerFunc
	Input       interface{}
}

type CoordinatedActivityHandlerStartFunc func(*swf.PollForActivityTaskOutput, interface{}) (interface{}, error)

type CoordinatedActivityHandlerTickFunc func(*swf.PollForActivityTaskOutput, interface{}) (bool, interface{}, error)

type CoordinatedActivityHandlerCancelFunc func(*swf.PollForActivityTaskOutput, interface{}) error

type CoordinatedActivityHandlerFinishFunc func(*swf.PollForActivityTaskOutput, interface{}) error

type CoordinatedActivityHandler struct {
	// Start is called when a new activity is ready to be handled.
	Start CoordinatedActivityHandlerStartFunc

	// Tick is called regularly to process a running activity.
	Tick CoordinatedActivityHandlerTickFunc

	// Cancel is called when a running activity receives a request to cancel
	// via heartbeat update.
	Cancel CoordinatedActivityHandlerCancelFunc

	// Finish is called at the end of handling every activity.
	// It is called no matter the outcome, eg if Start fails,
	// Tick decides to stop continuing, or the activity is canceled.
	Finish CoordinatedActivityHandlerFinishFunc

	Input    interface{}
	Activity string
}

func NewActivityHandler(activity string, handler interface{}) *ActivityHandler {
	input := inputType(handler, 1)
	output := outputType(handler, 0)
	newType := input
	if input.Kind() == reflect.Ptr {
		newType = input.Elem()
	}
	typeCheck(handler, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{output, "error"})
	return &ActivityHandler{
		Activity:    activity,
		HandlerFunc: marshalledFunc{reflect.ValueOf(handler)}.activityHandlerFunc,
		Input:       reflect.New(newType).Elem().Interface(),
	}
}

func NewCoordinatedActivityHandler(activity string, start interface{}, tick interface{}, cancel interface{}, finish interface{}) *CoordinatedActivityHandler {
	input := inputType(tick, 1)
	newType := input
	if input.Kind() == reflect.Ptr {
		newType = input.Elem()
	}
	output := outputType(tick, 1)

	typeCheck(start, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{output, "error"})
	typeCheck(tick, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{"bool", output, "error"})
	typeCheck(cancel, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{"error"})
	typeCheck(finish, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{"error"})

	return &CoordinatedActivityHandler{
		Activity: activity,
		Start:    marshalledFunc{reflect.ValueOf(start)}.handleCoordinatedActivityStart,
		Tick:     marshalledFunc{reflect.ValueOf(tick)}.handleCoordinatedActivityTick,
		Cancel:   marshalledFunc{reflect.ValueOf(cancel)}.handleCoordinatedActivityCancel,
		Finish:   marshalledFunc{reflect.ValueOf(finish)}.handleCoordinatedActivityFinish,
		Input:    reflect.New(newType).Elem().Interface(),
	}

}

func (a *ActivityHandler) ZeroInput() interface{} {
	return reflect.New(reflect.TypeOf(a.Input)).Interface()
}

type marshalledFunc struct {
	v reflect.Value
}

func (m marshalledFunc) activityHandlerFunc(task *swf.PollForActivityTaskOutput, input interface{}) (interface{}, error) {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return outputValue(ret[0]), errorValue(ret[1])
}

func (m marshalledFunc) longRunningActivityHandlerFunc(task *swf.PollForActivityTaskOutput, input interface{}) {
	m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
}

func (m marshalledFunc) handleCoordinatedActivityStart(task *swf.PollForActivityTaskOutput, input interface{}) (interface{}, error) {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return outputValue(ret[0]), errorValue(ret[1])
}

func (m marshalledFunc) handleCoordinatedActivityTick(task *swf.PollForActivityTaskOutput, input interface{}) (bool, interface{}, error) {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return outputValue(ret[0]).(bool), outputValue(ret[1]), errorValue(ret[2])
}

func (m marshalledFunc) handleCoordinatedActivityCancel(task *swf.PollForActivityTaskOutput, input interface{}) error {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return errorValue(ret[0])
}

func (m marshalledFunc) handleCoordinatedActivityFinish(task *swf.PollForActivityTaskOutput, input interface{}) error {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return errorValue(ret[0])
}

func outputValue(v reflect.Value) interface{} {
	switch v.Kind() {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
		if v.IsNil() {
			return nil
		} else {
			return v.Interface()
		}
	default:
		return v.Interface()
	}
}

func errorValue(v reflect.Value) error {
	if v.IsNil() {
		return nil
	}
	return v.Interface().(error)
}

func inputType(handler interface{}, idx int) reflect.Type {
	t := reflect.TypeOf(handler)
	if reflect.Func != t.Kind() {
		panic(fmt.Sprintf("kind was %v, not Func", t.Kind()))
	}
	return t.In(idx)
}

func outputType(handler interface{}, idx int) string {
	t := reflect.TypeOf(handler)
	if reflect.Func != t.Kind() {
		panic(fmt.Sprintf("kind was %v, not Func", t.Kind()))
	}
	return t.Out(idx).String()
}

func typeCheck(typedFunc interface{}, in []string, out []string) {
	t := reflect.TypeOf(typedFunc)
	if reflect.Func != t.Kind() {
		panic(fmt.Sprintf("kind was %v, not Func", t.Kind()))
	}
	if len(in) != t.NumIn() {
		panic(fmt.Sprintf(
			"input arity was %v, not %v",
			t.NumIn(), len(in),
		))
	}

	for i, rt := range in {
		if rt != t.In(i).String() {
			panic(fmt.Sprintf(
				"type of argument %v was %v, not %v",
				i, t.In(i), rt,
			))
		}
	}

	if len(out) != t.NumOut() {
		panic(fmt.Sprintf(
			"number of return values was %v, not %v",
			t.NumOut(), len(out),
		))
	}

	for i, rt := range out {
		if rt != t.Out(i).String() {
			panic(fmt.Sprintf(
				"type of return value %v was %v, not %v",
				i, t.Out(i), rt,
			))
		}
	}
}
<commit_msg>Add more explicit documentation to Tick field in CoordinatedActivityHandler<commit_after>package activity

import (
	"fmt"
	"reflect"

	"github.com/aws/aws-sdk-go/service/swf"
)

type ActivityHandlerFunc func(activityTask *swf.PollForActivityTaskOutput, input interface{}) (interface{}, error)

type ActivityHandler struct {
	Activity    string
	HandlerFunc ActivityHandlerFunc
	Input       interface{}
}

type CoordinatedActivityHandlerStartFunc func(*swf.PollForActivityTaskOutput, interface{}) (interface{}, error)

type CoordinatedActivityHandlerTickFunc func(*swf.PollForActivityTaskOutput, interface{}) (bool, interface{}, error)

type CoordinatedActivityHandlerCancelFunc func(*swf.PollForActivityTaskOutput, interface{}) error

type CoordinatedActivityHandlerFinishFunc func(*swf.PollForActivityTaskOutput, interface{}) error

type CoordinatedActivityHandler struct {
	// Start is called when a new activity is ready to be handled.
	Start CoordinatedActivityHandlerStartFunc

	// Tick is called regularly to process a running activity.
	// Tick that returns false, nil, nil just expresses that the job is still running.
	// Tick that returns false, &SomeStruct{}, nil will express that the job is still running and also send an 'ActivityUpdated' signal back to the FSM with SomeStruct{} as the Input.
	// Tick that returns true, &SomeStruct{}, nil, expresses that the job/activity is done and send SomeStruct{} back as the result. as well as stops heartbeating.
	// Tick that returns true, nil, nil, expresses that the job is done and send no result back, as well as stops heartbeating.
	// Tick that returns false, nil, err expresses that the job/activity failed and sends back err as the reason. as well as stops heartbeating.
	Tick CoordinatedActivityHandlerTickFunc

	// Cancel is called when a running activity receives a request to cancel
	// via heartbeat update.
	Cancel CoordinatedActivityHandlerCancelFunc

	// Finish is called at the end of handling every activity.
	// It is called no matter the outcome, eg if Start fails,
	// Tick decides to stop continuing, or the activity is canceled.
	Finish CoordinatedActivityHandlerFinishFunc

	Input    interface{}
	Activity string
}

func NewActivityHandler(activity string, handler interface{}) *ActivityHandler {
	input := inputType(handler, 1)
	output := outputType(handler, 0)
	newType := input
	if input.Kind() == reflect.Ptr {
		newType = input.Elem()
	}
	typeCheck(handler, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{output, "error"})
	return &ActivityHandler{
		Activity:    activity,
		HandlerFunc: marshalledFunc{reflect.ValueOf(handler)}.activityHandlerFunc,
		Input:       reflect.New(newType).Elem().Interface(),
	}
}

func NewCoordinatedActivityHandler(activity string, start interface{}, tick interface{}, cancel interface{}, finish interface{}) *CoordinatedActivityHandler {
	input := inputType(tick, 1)
	newType := input
	if input.Kind() == reflect.Ptr {
		newType = input.Elem()
	}
	output := outputType(tick, 1)

	typeCheck(start, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{output, "error"})
	typeCheck(tick, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{"bool", output, "error"})
	typeCheck(cancel, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{"error"})
	typeCheck(finish, []string{"*swf.PollForActivityTaskOutput", input.String()}, []string{"error"})

	return &CoordinatedActivityHandler{
		Activity: activity,
		Start:    marshalledFunc{reflect.ValueOf(start)}.handleCoordinatedActivityStart,
		Tick:     marshalledFunc{reflect.ValueOf(tick)}.handleCoordinatedActivityTick,
		Cancel:   marshalledFunc{reflect.ValueOf(cancel)}.handleCoordinatedActivityCancel,
		Finish:   marshalledFunc{reflect.ValueOf(finish)}.handleCoordinatedActivityFinish,
		Input:    reflect.New(newType).Elem().Interface(),
	}

}

func (a *ActivityHandler) ZeroInput() interface{} {
	return reflect.New(reflect.TypeOf(a.Input)).Interface()
}

type marshalledFunc struct {
	v reflect.Value
}

func (m marshalledFunc) activityHandlerFunc(task *swf.PollForActivityTaskOutput, input interface{}) (interface{}, error) {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return outputValue(ret[0]), errorValue(ret[1])
}

func (m marshalledFunc) longRunningActivityHandlerFunc(task *swf.PollForActivityTaskOutput, input interface{}) {
	m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
}

func (m marshalledFunc) handleCoordinatedActivityStart(task *swf.PollForActivityTaskOutput, input interface{}) (interface{}, error) {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return outputValue(ret[0]), errorValue(ret[1])
}

func (m marshalledFunc) handleCoordinatedActivityTick(task *swf.PollForActivityTaskOutput, input interface{}) (bool, interface{}, error) {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return outputValue(ret[0]).(bool), outputValue(ret[1]), errorValue(ret[2])
}

func (m marshalledFunc) handleCoordinatedActivityCancel(task *swf.PollForActivityTaskOutput, input interface{}) error {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return errorValue(ret[0])
}

func (m marshalledFunc) handleCoordinatedActivityFinish(task *swf.PollForActivityTaskOutput, input interface{}) error {
	ret := m.v.Call([]reflect.Value{reflect.ValueOf(task), reflect.ValueOf(input)})
	return errorValue(ret[0])
}

func outputValue(v reflect.Value) interface{} {
	switch v.Kind() {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
		if v.IsNil() {
			return nil
		} else {
			return v.Interface()
		}
	default:
		return v.Interface()
	}
}

func errorValue(v reflect.Value) error {
	if v.IsNil() {
		return nil
	}
	return v.Interface().(error)
}

func inputType(handler interface{}, idx int) reflect.Type {
	t := reflect.TypeOf(handler)
	if reflect.Func != t.Kind() {
		panic(fmt.Sprintf("kind was %v, not Func", t.Kind()))
	}
	return t.In(idx)
}

func outputType(handler interface{}, idx int) string {
	t := reflect.TypeOf(handler)
	if reflect.Func != t.Kind() {
		panic(fmt.Sprintf("kind was %v, not Func", t.Kind()))
	}
	return t.Out(idx).String()
}

func typeCheck(typedFunc interface{}, in []string, out []string) {
	t := reflect.TypeOf(typedFunc)
	if reflect.Func != t.Kind() {
		panic(fmt.Sprintf("kind was %v, not Func", t.Kind()))
	}
	if len(in) != t.NumIn() {
		panic(fmt.Sprintf(
			"input arity was %v, not %v",
			t.NumIn(), len(in),
		))
	}

	for i, rt := range in {
		if rt != t.In(i).String() {
			panic(fmt.Sprintf(
				"type of argument %v was %v, not %v",
				i, t.In(i), rt,
			))
		}
	}

	if len(out) != t.NumOut() {
		panic(fmt.Sprintf(
			"number of return values was %v, not %v",
			t.NumOut(), len(out),
		))
	}

	for i, rt := range out {
		if rt != t.Out(i).String() {
			panic(fmt.Sprintf(
				"type of return value %v was %v, not %v",
				i, t.Out(i), rt,
			))
		}
	}
}
<|endoftext|>


<commit_before>package namecheap

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"

	"golang.org/x/net/publicsuffix"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/StackExchange/dnscontrol/providers"
	"github.com/StackExchange/dnscontrol/providers/diff"
	nc "github.com/billputer/go-namecheap"
	"github.com/miekg/dns/dnsutil"
)

var NamecheapDefaultNs = []string{"dns1.registrar-servers.com", "dns2.registrar-servers.com"}

type Namecheap struct {
	ApiKey  string
	ApiUser string
	client  *nc.Client
}

var docNotes = providers.DocumentationNotes{
	providers.DocCreateDomains:       providers.Cannot("Requires domain registered through their service"),
	providers.DocOfficiallySupported: providers.Cannot(),
	providers.DocDualHost:            providers.Cannot("Doesn't allow control of apex NS records"),
	providers.CanUseAlias:            providers.Cannot(),
	providers.CanUseCAA:              providers.Cannot(),
	providers.CanUseSRV:              providers.Unimplemented("namecheap supports srv records, we just need someone to implement it and make sure the tests pass."),
	providers.CanUsePTR:              providers.Cannot(),
	providers.CanUseTLSA:             providers.Cannot(),
}

func init() {
	providers.RegisterRegistrarType("NAMECHEAP", newReg, docNotes)
	providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, providers.CantUseNOPURGE)
}

func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
	return newProvider(conf, metadata)
}

func newReg(conf map[string]string) (providers.Registrar, error) {
	return newProvider(conf, nil)
}

func newProvider(m map[string]string, metadata json.RawMessage) (*Namecheap, error) {
	api := &Namecheap{}
	api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
	if api.ApiKey == "" || api.ApiUser == "" {
		return nil, fmt.Errorf("Namecheap apikey and apiuser must be provided.")
	}
	api.client = nc.NewClient(api.ApiUser, api.ApiKey, api.ApiUser)
	// if BaseURL is specified in creds, use that url
	BaseURL, ok := m["BaseURL"]
	if ok {
		api.client.BaseURL = BaseURL
	}
	return api, nil
}

func splitDomain(domain string) (sld string, tld string) {
	tld, _ = publicsuffix.PublicSuffix(domain)
	d, _ := publicsuffix.EffectiveTLDPlusOne(domain)
	sld = strings.Split(d, ".")[0]
	return sld, tld
}

// namecheap has request limiting at unpublished limits
// this channel acts as a global rate limiter
// read from it before every request
// from support in SEP-2017:
//    "The limits for the API calls will be 20/Min, 700/Hour and 8000/Day for one user.
//     If you can limit the requests within these it should be fine."
var throttle = make(chan bool, 20)

func init() {
	go func() {
		for {
			// add (up to) 20 requests every minute
			for i := 0; i < 20; i++ {
				select {
				case throttle <- true:
				default:
				}
			}
		}
	}()
}

func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	dc.Punycode()
	sld, tld := splitDomain(dc.Name)
	<-throttle
	records, err := n.client.DomainsDNSGetHosts(sld, tld)
	if err != nil {
		return nil, err
	}

	var actual []*models.RecordConfig

	// namecheap does not allow setting @ NS with basic DNS
	dc.Filter(func(r *models.RecordConfig) bool {
		if r.Type == "NS" && r.Name == "@" {
			if !strings.HasSuffix(r.Target, "registrar-servers.com.") {
				fmt.Println("\n", r.Target, "Namecheap does not support changing apex NS records. Skipping.")
			}
			return false
		}
		return true
	})

	// namecheap has this really annoying feature where they add some parking records if you have no records.
	// This causes a few problems for our purposes, specifically the integration tests.
	// lets detect that one case and pretend it is a no-op.
	if len(dc.Records) == 0 && len(records.Hosts) == 2 {
		if records.Hosts[0].Type == "CNAME" &&
			strings.Contains(records.Hosts[0].Address, "parkingpage") &&
			records.Hosts[1].Type == "URL" {
			return nil, nil
		}
	}

	for _, r := range records.Hosts {
		if r.Type == "SOA" {
			continue
		}
		rec := &models.RecordConfig{
			NameFQDN:     dnsutil.AddOrigin(r.Name, dc.Name),
			Type:         r.Type,
			Target:       r.Address,
			TTL:          uint32(r.TTL),
			MxPreference: uint16(r.MXPref),
			Original:     r,
		}
		actual = append(actual, rec)
	}

	differ := diff.New(dc)
	_, create, delete, modify := differ.IncrementalDiff(actual)

	// // because namecheap doesn't have selective create, delete, modify,
	// // we bundle them all up to send at once.  We *do* want to see the
	// // changes though

	var desc []string
	for _, i := range create {
		desc = append(desc, "\n"+i.String())
	}
	for _, i := range delete {
		desc = append(desc, "\n"+i.String())
	}
	for _, i := range modify {
		desc = append(desc, "\n"+i.String())
	}

	msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc)
	corrections := []*models.Correction{}

	// only create corrections if there are changes
	if len(desc) > 0 {
		corrections = append(corrections,
			&models.Correction{
				Msg: msg,
				F: func() error {
					return n.generateRecords(dc)
				},
			})
	}

	return corrections, nil
}

func (n *Namecheap) generateRecords(dc *models.DomainConfig) error {

	var recs []nc.DomainDNSHost

	id := 1
	for _, r := range dc.Records {
		name := dnsutil.TrimDomainName(r.NameFQDN, dc.Name)
		rec := nc.DomainDNSHost{
			ID:      id,
			Name:    name,
			Type:    r.Type,
			Address: r.Target,
			MXPref:  int(r.MxPreference),
			TTL:     int(r.TTL),
		}
		recs = append(recs, rec)
		id++
	}
	sld, tld := splitDomain(dc.Name)
	<-throttle
	_, err := n.client.DomainDNSSetHosts(sld, tld, recs)
	return err
}

func (n *Namecheap) GetNameservers(domainName string) ([]*models.Nameserver, error) {
	// return default namecheap nameservers
	ns := NamecheapDefaultNs

	return models.StringsToNameservers(ns), nil
}

func (n *Namecheap) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	<-throttle
	info, err := n.client.DomainGetInfo(dc.Name)
	if err != nil {
		return nil, err
	}
	sort.Strings(info.DNSDetails.Nameservers)
	found := strings.Join(info.DNSDetails.Nameservers, ",")
	desiredNs := []string{}
	for _, d := range dc.Nameservers {
		desiredNs = append(desiredNs, d.Name)
	}
	sort.Strings(desiredNs)
	desired := strings.Join(desiredNs, ",")
	if found != desired {
		parts := strings.SplitN(dc.Name, ".", 2)
		sld, tld := parts[0], parts[1]
		return []*models.Correction{
			{
				Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", found, desired),
				F: func() error {
					<-throttle
					_, err := n.client.DomainDNSSetCustom(sld, tld, desired)
					if err != nil {
						return err
					}
					return nil
				}},
		}, nil
	}
	return nil, nil
}
<commit_msg>re-add sleep to namecheap<commit_after>package namecheap

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"time"

	"golang.org/x/net/publicsuffix"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/StackExchange/dnscontrol/providers"
	"github.com/StackExchange/dnscontrol/providers/diff"
	nc "github.com/billputer/go-namecheap"
	"github.com/miekg/dns/dnsutil"
)

var NamecheapDefaultNs = []string{"dns1.registrar-servers.com", "dns2.registrar-servers.com"}

type Namecheap struct {
	ApiKey  string
	ApiUser string
	client  *nc.Client
}

var docNotes = providers.DocumentationNotes{
	providers.DocCreateDomains:       providers.Cannot("Requires domain registered through their service"),
	providers.DocOfficiallySupported: providers.Cannot(),
	providers.DocDualHost:            providers.Cannot("Doesn't allow control of apex NS records"),
	providers.CanUseAlias:            providers.Cannot(),
	providers.CanUseCAA:              providers.Cannot(),
	providers.CanUseSRV:              providers.Unimplemented("namecheap supports srv records, we just need someone to implement it and make sure the tests pass."),
	providers.CanUsePTR:              providers.Cannot(),
	providers.CanUseTLSA:             providers.Cannot(),
}

func init() {
	providers.RegisterRegistrarType("NAMECHEAP", newReg, docNotes)
	providers.RegisterDomainServiceProviderType("NAMECHEAP", newDsp, providers.CantUseNOPURGE)
}

func newDsp(conf map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
	return newProvider(conf, metadata)
}

func newReg(conf map[string]string) (providers.Registrar, error) {
	return newProvider(conf, nil)
}

func newProvider(m map[string]string, metadata json.RawMessage) (*Namecheap, error) {
	api := &Namecheap{}
	api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
	if api.ApiKey == "" || api.ApiUser == "" {
		return nil, fmt.Errorf("Namecheap apikey and apiuser must be provided.")
	}
	api.client = nc.NewClient(api.ApiUser, api.ApiKey, api.ApiUser)
	// if BaseURL is specified in creds, use that url
	BaseURL, ok := m["BaseURL"]
	if ok {
		api.client.BaseURL = BaseURL
	}
	return api, nil
}

func splitDomain(domain string) (sld string, tld string) {
	tld, _ = publicsuffix.PublicSuffix(domain)
	d, _ := publicsuffix.EffectiveTLDPlusOne(domain)
	sld = strings.Split(d, ".")[0]
	return sld, tld
}

// namecheap has request limiting at unpublished limits
// this channel acts as a global rate limiter
// read from it before every request
// from support in SEP-2017:
//    "The limits for the API calls will be 20/Min, 700/Hour and 8000/Day for one user.
//     If you can limit the requests within these it should be fine."
var throttle = make(chan bool, 20)

func init() {
	go func() {
		for {
			// add (up to) 20 requests every minute
			for i := 0; i < 20; i++ {
				select {
				case throttle <- true:
				default:
				}
			}
			time.Sleep(time.Minute)
		}
	}()
}

func (n *Namecheap) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	dc.Punycode()
	sld, tld := splitDomain(dc.Name)
	<-throttle
	records, err := n.client.DomainsDNSGetHosts(sld, tld)
	if err != nil {
		return nil, err
	}

	var actual []*models.RecordConfig

	// namecheap does not allow setting @ NS with basic DNS
	dc.Filter(func(r *models.RecordConfig) bool {
		if r.Type == "NS" && r.Name == "@" {
			if !strings.HasSuffix(r.Target, "registrar-servers.com.") {
				fmt.Println("\n", r.Target, "Namecheap does not support changing apex NS records. Skipping.")
			}
			return false
		}
		return true
	})

	// namecheap has this really annoying feature where they add some parking records if you have no records.
	// This causes a few problems for our purposes, specifically the integration tests.
	// lets detect that one case and pretend it is a no-op.
	if len(dc.Records) == 0 && len(records.Hosts) == 2 {
		if records.Hosts[0].Type == "CNAME" &&
			strings.Contains(records.Hosts[0].Address, "parkingpage") &&
			records.Hosts[1].Type == "URL" {
			return nil, nil
		}
	}

	for _, r := range records.Hosts {
		if r.Type == "SOA" {
			continue
		}
		rec := &models.RecordConfig{
			NameFQDN:     dnsutil.AddOrigin(r.Name, dc.Name),
			Type:         r.Type,
			Target:       r.Address,
			TTL:          uint32(r.TTL),
			MxPreference: uint16(r.MXPref),
			Original:     r,
		}
		actual = append(actual, rec)
	}

	differ := diff.New(dc)
	_, create, delete, modify := differ.IncrementalDiff(actual)

	// // because namecheap doesn't have selective create, delete, modify,
	// // we bundle them all up to send at once.  We *do* want to see the
	// // changes though

	var desc []string
	for _, i := range create {
		desc = append(desc, "\n"+i.String())
	}
	for _, i := range delete {
		desc = append(desc, "\n"+i.String())
	}
	for _, i := range modify {
		desc = append(desc, "\n"+i.String())
	}

	msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc)
	corrections := []*models.Correction{}

	// only create corrections if there are changes
	if len(desc) > 0 {
		corrections = append(corrections,
			&models.Correction{
				Msg: msg,
				F: func() error {
					return n.generateRecords(dc)
				},
			})
	}

	return corrections, nil
}

func (n *Namecheap) generateRecords(dc *models.DomainConfig) error {

	var recs []nc.DomainDNSHost

	id := 1
	for _, r := range dc.Records {
		name := dnsutil.TrimDomainName(r.NameFQDN, dc.Name)
		rec := nc.DomainDNSHost{
			ID:      id,
			Name:    name,
			Type:    r.Type,
			Address: r.Target,
			MXPref:  int(r.MxPreference),
			TTL:     int(r.TTL),
		}
		recs = append(recs, rec)
		id++
	}
	sld, tld := splitDomain(dc.Name)
	<-throttle
	_, err := n.client.DomainDNSSetHosts(sld, tld, recs)
	return err
}

func (n *Namecheap) GetNameservers(domainName string) ([]*models.Nameserver, error) {
	// return default namecheap nameservers
	ns := NamecheapDefaultNs

	return models.StringsToNameservers(ns), nil
}

func (n *Namecheap) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	<-throttle
	info, err := n.client.DomainGetInfo(dc.Name)
	if err != nil {
		return nil, err
	}
	sort.Strings(info.DNSDetails.Nameservers)
	found := strings.Join(info.DNSDetails.Nameservers, ",")
	desiredNs := []string{}
	for _, d := range dc.Nameservers {
		desiredNs = append(desiredNs, d.Name)
	}
	sort.Strings(desiredNs)
	desired := strings.Join(desiredNs, ",")
	if found != desired {
		parts := strings.SplitN(dc.Name, ".", 2)
		sld, tld := parts[0], parts[1]
		return []*models.Correction{
			{
				Msg: fmt.Sprintf("Change Nameservers from '%s' to '%s'", found, desired),
				F: func() error {
					<-throttle
					_, err := n.client.DomainDNSSetCustom(sld, tld, desired)
					if err != nil {
						return err
					}
					return nil
				}},
		}, nil
	}
	return nil, nil
}
<|endoftext|>


<commit_before>/*Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cephinstaller

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/parnurzeal/gorequest"
	"github.com/skyrings/skyring-common/conf"
	"github.com/skyrings/skyring-common/models"
	"github.com/skyrings/skyring-common/provisioner"
	"github.com/skyrings/skyring-common/tools/logger"
	"github.com/skyrings/skyring-common/tools/task"
	"github.com/skyrings/skyring-common/utils"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"
)

type CephInstaller struct {
}

const (
	ProviderName                      = "ceph-installer"
	CEPH_INSTALLER_API_DEFAULT_PREFIX = "api"
	CEPH_INSTALLER_API_PORT           = 8181
	MON                               = "MON"
	OSD                               = "OSD"
)

type InstallReq struct {
	Hosts         []string `json:"hosts"`
	RedhatStorage bool     `json:"redhat_storage"`
	RedhatUseCdn  bool     `json:"redhat_use_cdn"`
}

type Task struct {
	Endpoint   string `json:"endpoint"`
	Succeeded  bool   `json:"succeeded"`
	Stdout     string `json:"stdout"`
	Started    string `json:"started"`
	ExitCode   int    `json:"exit_code"`
	Ended      string `json:"ended"`
	Command    string `json:"command"`
	Stderr     string `json:"stderr"`
	Identifier string `json:"identifier"`
}

var httprequest = gorequest.New()

func init() {
	provisioner.RegisterProvider(ProviderName, func(config io.Reader) (provisioner.Provisioner, error) {
		return New(config)
	})
}

func New(config io.Reader) (*CephInstaller, error) {
	installer := new(CephInstaller)
	installer.LoadRoutes()
	return installer, nil
}

func (c CephInstaller) Install(ctxt string, t *task.Task, providerName string, nodes []models.ClusterNode) []models.ClusterNode {

	db_nodes, err := util.GetNodesByIdStr(nodes)
	if err != nil {
		logger.Get().Error("%s-Error getting nodes while package installation: %v", ctxt, err)
		return nodes
	}

	var (
		wg          sync.WaitGroup
		failedNodes []models.ClusterNode
		syncMutex   sync.Mutex
	)

	for _, node := range nodes {
		wg.Add(1)
		go func(node models.ClusterNode, hostname string) {
			defer wg.Done()
			var route models.ApiRoute
			hosts := []string{hostname}
			data := make(map[string]interface{})
			if util.StringInSlice(MON, node.NodeType) {
				route = CEPH_INSTALLER_API_ROUTES["monInstall"]
				data["calamari"] = true
			} else if util.StringInSlice(OSD, node.NodeType) {
				route = CEPH_INSTALLER_API_ROUTES["osdInstall"]
			}

			data["hosts"] = hosts
			data["redhat_storage"] = conf.SystemConfig.Provisioners[providerName].RedhatStorage

			reqData, err := formConfigureData(data)
			if err != nil {
				failedNodes = append(failedNodes, node)
				return
			}

			resp, body, errs := httprequest.Post(formUrl(route)).Send(reqData).End()

			respData, err := parseInstallResponseData(ctxt, resp, body, errs)
			if err != nil {
				syncMutex.Lock()
				defer syncMutex.Unlock()
				failedNodes = append(failedNodes, node)
				return
			}
			if ok := syncRequestStatus(ctxt, respData.Identifier); !ok {
				syncMutex.Lock()
				defer syncMutex.Unlock()
				failedNodes = append(failedNodes, node)
				return
			}
			t.UpdateStatus("Installed packages on: %s", hostname)
		}(node, db_nodes[node.NodeId].Hostname)
	}
	wg.Wait()

	return failedNodes
}
func (c CephInstaller) Configure(ctxt string, t *task.Task, reqType string, data map[string]interface{}) error {
	var (
		resp  *http.Response
		body  string
		errs  []error
		route models.ApiRoute
	)
	reqData, err := formConfigureData(data)
	if err != nil {
		return err
	}
	if reqType == MON {
		route = CEPH_INSTALLER_API_ROUTES["monConfigure"]

	} else if reqType == OSD {
		route = CEPH_INSTALLER_API_ROUTES["osdConfigure"]

	}

	resp, body, errs = httprequest.Post(formUrl(route)).Send(reqData).End()

	respData, err := parseInstallResponseData(ctxt, resp, body, errs)
	if err != nil {
		return err
	}
	if ok := syncRequestStatus(ctxt, respData.Identifier); !ok {
		return errors.New("MON Configuration Failed")
	}
	return nil
}

func formUrl(route models.ApiRoute) string {
	url := fmt.Sprintf("http://localhost:%d/%s/%s", CEPH_INSTALLER_API_PORT, CEPH_INSTALLER_API_DEFAULT_PREFIX, route.Pattern)
	return url
}

func syncRequestStatus(ctxt string, taskId string) bool {
	var status bool

	for {
		time.Sleep(10 * time.Second)
		taskData, err := getTaskData(ctxt, taskId)
		if err != nil {
			return status
		}

		if taskData.Ended != "" {
			// If request has failed return with error
			if taskData.Succeeded {
				status = true
			} else {
				logger.Get().Error("%s-Error while installing Packages. stdout: %v. stderr:%v", ctxt, taskData.Stdout, taskData.Stderr)
			}
			break
		}
	}

	return status
}

func getTaskData(ctxt string, taskId string) (Task, error) {

	var respData Task
	route := CEPH_INSTALLER_API_ROUTES["taskStatus"]
	route.Pattern = strings.Replace(route.Pattern, "{id}", taskId, 1)
	resp, body, errs := httprequest.Get(formUrl(route)).End()
	if len(errs) > 0 {
		logger.Get().Error("%s-Error getting task status for task:%v.Err:%v", ctxt, taskId, errs)
		return respData, errors.New("Error getting task status")
	}
	if resp.StatusCode != http.StatusOK {
		logger.Get().Error("%s-Error Getting Task Status:%v", ctxt, taskId)
		return respData, errors.New("Error getting task status")
	}

	if err := json.Unmarshal([]byte(body), &respData); err != nil {
		logger.Get().Error("%s-Unable to unmarshal response. error: %v", ctxt, err)
		return respData, errors.New("Unable to unmarshal response")
	}
	return respData, nil
}

func parseInstallResponseData(ctxt string, resp *http.Response, body string, errs []error) (Task, error) {
	var respData Task
	if len(errs) > 0 {
		logger.Get().Error("%s-Error Installing Packages:%v", ctxt, errs)
		return respData, errors.New("Error Sending ceph-installer request")
	}
	if resp.StatusCode != http.StatusOK {
		logger.Get().Error("%s-Error Sending ceph-installer request", ctxt)
		return respData, errors.New("Error Sending ceph-installer request. Ceph-installer failed")
	}
	if err := json.Unmarshal([]byte(body), &respData); err != nil {
		logger.Get().Error("%s-Unable to unmarshal response. error: %v", ctxt, err)
		return respData, errors.New("Unable to unmarshal response")
	}
	return respData, nil
}

func formConfigureData(data map[string]interface{}) (string, error) {

	request, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	return string(request), nil

}
<commit_msg>skyring-common: Log the provisioner call details<commit_after>/*Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cephinstaller

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/parnurzeal/gorequest"
	"github.com/skyrings/skyring-common/conf"
	"github.com/skyrings/skyring-common/models"
	"github.com/skyrings/skyring-common/provisioner"
	"github.com/skyrings/skyring-common/tools/logger"
	"github.com/skyrings/skyring-common/tools/task"
	"github.com/skyrings/skyring-common/utils"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"
)

type CephInstaller struct {
}

const (
	ProviderName                      = "ceph-installer"
	CEPH_INSTALLER_API_DEFAULT_PREFIX = "api"
	CEPH_INSTALLER_API_PORT           = 8181
	MON                               = "MON"
	OSD                               = "OSD"
)

type InstallReq struct {
	Hosts         []string `json:"hosts"`
	RedhatStorage bool     `json:"redhat_storage"`
	RedhatUseCdn  bool     `json:"redhat_use_cdn"`
}

type Task struct {
	Endpoint   string `json:"endpoint"`
	Succeeded  bool   `json:"succeeded"`
	Stdout     string `json:"stdout"`
	Started    string `json:"started"`
	ExitCode   int    `json:"exit_code"`
	Ended      string `json:"ended"`
	Command    string `json:"command"`
	Stderr     string `json:"stderr"`
	Identifier string `json:"identifier"`
}

var httprequest = gorequest.New()

func init() {
	provisioner.RegisterProvider(ProviderName, func(config io.Reader) (provisioner.Provisioner, error) {
		return New(config)
	})
}

func New(config io.Reader) (*CephInstaller, error) {
	installer := new(CephInstaller)
	installer.LoadRoutes()
	return installer, nil
}

func (c CephInstaller) Install(ctxt string, t *task.Task, providerName string, nodes []models.ClusterNode) []models.ClusterNode {

	db_nodes, err := util.GetNodesByIdStr(nodes)
	if err != nil {
		logger.Get().Error("%s-Error getting nodes while package installation: %v", ctxt, err)
		return nodes
	}

	var (
		wg          sync.WaitGroup
		failedNodes []models.ClusterNode
		syncMutex   sync.Mutex
	)

	for _, node := range nodes {
		wg.Add(1)
		go func(node models.ClusterNode, hostname string) {
			defer wg.Done()
			var route models.ApiRoute
			hosts := []string{hostname}
			data := make(map[string]interface{})
			if util.StringInSlice(MON, node.NodeType) {
				route = CEPH_INSTALLER_API_ROUTES["monInstall"]
				data["calamari"] = true
			} else if util.StringInSlice(OSD, node.NodeType) {
				route = CEPH_INSTALLER_API_ROUTES["osdInstall"]
			}

			data["hosts"] = hosts
			data["redhat_storage"] = conf.SystemConfig.Provisioners[providerName].RedhatStorage

			reqData, err := formConfigureData(data)
			if err != nil {
				failedNodes = append(failedNodes, node)
				return
			}

			resp, body, errs := httprequest.Post(formUrl(route)).Send(reqData).End()

			respData, err := parseInstallResponseData(ctxt, resp, body, errs)
			if err != nil {
				syncMutex.Lock()
				defer syncMutex.Unlock()
				failedNodes = append(failedNodes, node)
				return
			}
			logger.Get().Info(
				"%s-Started installation on node :%s. TaskId: %s. Request Data: %v. Route: %s",
				ctxt,
				hostname,
				respData.Identifier,
				data,
				formUrl(route))
			if ok := syncRequestStatus(ctxt, respData.Identifier); !ok {
				syncMutex.Lock()
				defer syncMutex.Unlock()
				failedNodes = append(failedNodes, node)
				return
			}
			t.UpdateStatus("Installed packages on: %s", hostname)
		}(node, db_nodes[node.NodeId].Hostname)
	}
	wg.Wait()

	return failedNodes
}
func (c CephInstaller) Configure(ctxt string, t *task.Task, reqType string, data map[string]interface{}) error {
	var (
		resp  *http.Response
		body  string
		errs  []error
		route models.ApiRoute
	)
	reqData, err := formConfigureData(data)
	if err != nil {
		return err
	}
	if reqType == MON {
		route = CEPH_INSTALLER_API_ROUTES["monConfigure"]

	} else if reqType == OSD {
		route = CEPH_INSTALLER_API_ROUTES["osdConfigure"]

	}

	resp, body, errs = httprequest.Post(formUrl(route)).Send(reqData).End()

	respData, err := parseInstallResponseData(ctxt, resp, body, errs)
	if err != nil {
		return err
	}
	logger.Get().Info(
		"%s-Started configuration on node: %s. TaskId: %s. Request Data: %v. Route: %s",
		ctxt,
		data["host"].(string),
		respData.Identifier,
		reqData,
		formUrl(route))
	if ok := syncRequestStatus(ctxt, respData.Identifier); !ok {
		return errors.New("MON Configuration Failed")
	}
	return nil
}

func formUrl(route models.ApiRoute) string {
	url := fmt.Sprintf("http://localhost:%d/%s/%s", CEPH_INSTALLER_API_PORT, CEPH_INSTALLER_API_DEFAULT_PREFIX, route.Pattern)
	return url
}

func syncRequestStatus(ctxt string, taskId string) bool {
	var status bool

	for {
		time.Sleep(10 * time.Second)
		taskData, err := getTaskData(ctxt, taskId)
		if err != nil {
			return status
		}

		if taskData.Ended != "" {
			// If request has failed return with error
			if taskData.Succeeded {
				status = true
			} else {
				logger.Get().Error("%s-Error while installing Packages. stdout: %v. stderr:%v", ctxt, taskData.Stdout, taskData.Stderr)
			}
			break
		}
	}

	return status
}

func getTaskData(ctxt string, taskId string) (Task, error) {

	var respData Task
	route := CEPH_INSTALLER_API_ROUTES["taskStatus"]
	route.Pattern = strings.Replace(route.Pattern, "{id}", taskId, 1)
	resp, body, errs := httprequest.Get(formUrl(route)).End()
	if len(errs) > 0 {
		logger.Get().Error("%s-Error getting task status for task:%v.Err:%v", ctxt, taskId, errs)
		return respData, errors.New("Error getting task status")
	}
	if resp.StatusCode != http.StatusOK {
		logger.Get().Error("%s-Error Getting Task Status:%v", ctxt, taskId)
		return respData, errors.New("Error getting task status")
	}

	if err := json.Unmarshal([]byte(body), &respData); err != nil {
		logger.Get().Error("%s-Unable to unmarshal response. error: %v", ctxt, err)
		return respData, errors.New("Unable to unmarshal response")
	}
	return respData, nil
}

func parseInstallResponseData(ctxt string, resp *http.Response, body string, errs []error) (Task, error) {
	var respData Task
	if len(errs) > 0 {
		logger.Get().Error("%s-Error Installing Packages:%v", ctxt, errs)
		return respData, errors.New("Error Sending ceph-installer request")
	}
	if resp.StatusCode != http.StatusOK {
		logger.Get().Error("%s-Error Sending ceph-installer request", ctxt)
		return respData, errors.New("Error Sending ceph-installer request. Ceph-installer failed")
	}
	if err := json.Unmarshal([]byte(body), &respData); err != nil {
		logger.Get().Error("%s-Unable to unmarshal response. error: %v", ctxt, err)
		return respData, errors.New("Unable to unmarshal response")
	}
	return respData, nil
}

func formConfigureData(data map[string]interface{}) (string, error) {

	request, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	return string(request), nil

}
<|endoftext|>


<commit_before>package main

import "fmt"
import "os"
import "strings"
import "regexp"
import "flag"
import "bufio"
import "log"
import "github.com/joncrlsn/pgutil"
import "github.com/joncrlsn/fileutil"
import "github.com/joncrlsn/misc"

// End of Statement regex
var eosRegex *regexp.Regexp = regexp.MustCompile(`;\s*$|;\s*--.*$`)
var inReader *bufio.Reader = bufio.NewReader(os.Stdin)

// Executes a file of SQL statements one statement at a time, stopping everything
// if one of them has an error
func main() {

    // -f (filename) is a required program argument
	var fileName string
	flag.StringVar(&fileName, "f", "", "path of the SQL file to run")
	dbInfo := pgutil.DbInfo{}
	dbInfo.DbOptions = "sslmode=disable"
	dbInfo.Populate()

	if len(fileName) == 0 {
        fmt.Println("Missing required filename argument (-f)")
		usage()
	}

	exists, _ := fileutil.Exists(fileName)
	if !exists {
		fmt.Fprintf(os.Stderr, "File does not exist: %s\n", fileName)
		os.Exit(2)
	}

    runFile(fileName, &dbInfo)
}

// Reads the file and runs the SQL statements one by one
func runFile(fileName string, dbInfo *pgutil.DbInfo) {
    // Open connection to the database
    db, err := dbInfo.Open()
    check("opening database", err)

    // Read each statement from the file one at a time and execute them
	sqlChan := sqlStatements(fileName)
	for sql := range sqlChan {
		// Execute SQL.  If not successful, stop and ask user
		// whether or not we should continue
		fmt.Println("================================")
		fmt.Println("Executing SQL: ", sql)
        result, err := db.Exec(sql)

        // If there was an error, ask user whether or not we should continue
		if err != nil {
            log.Println("Error:", err)
            if misc.PromptYesNo("SQL failed!  Do you want to continue?", false)  {
                continue
            }
            os.Exit(1)
		}

        rowCnt, err := result.RowsAffected()
        check("getting rows affected count", err)
        fmt.Printf("Rows affected: %d\n", rowCnt)
	}
    fmt.Println("Done!")
}

/*
 * Reads and returns (via channel) SQL statements from the given file.
 * SQL statements must end with a semi-colon
 */
func sqlStatements(fileName string) <-chan string {
	statementChan := make(chan string)

	go func() {
		lineChan := fileutil.ReadLinesChannel(fileName)

		// TODO: Convert this to a string builder
		statement := ""
		for line := range lineChan {
			//fmt.Printf("  Line: %s\n", line)

			// ignore blank or empty lines
			if len(strings.TrimSpace(line)) == 0 {
				continue
			}

			statement += line + "\n"

			// look for line ending with just a semi-colon
			// or a semi-colon with a SQL comment following
			if eosRegex.MatchString(line) {
				statementChan <- statement
				statement = ""
			}
		}

		close(statementChan)
	}()

	return statementChan
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s -f <sqlFileName> [-host <string>] [-port <int>] [-db <string>] [-user <string>] [-pw <password>] \n", os.Args[0])
    fmt.Fprintln(os.Stderr, `
Database connection properties can be specified in two ways:
  * Environment variables
  * Program flags (override environment variables)
  * ~/.pgpass file (for the password)

Environment variables are:
  DBHOST : host name where database is running (default is localhost)
  DBPORT : port database is listening on (default is 5432)
  DBNAME : name of database you want to update
  DBUSER : user in postgres you'll be executing the commands as
  DBPASS : password for the user

Program flags are:
  -f     : required. file path to the SQL
  -U     : user in postgres to execute the commands (matches psql flag)
  -h     : host name where database is running--default is localhost (matches psql flag)
  -p     : port.  defaults to 5432 (matches psql flag)
  -d     : database name (matches psql flag)
  -pw    : password for the user`)
	os.Exit(2)
}

func check(msg string, err error) {
    if err != nil {
        log.Fatal("Error " + msg, err)
    }
}
<commit_msg>minor improvements<commit_after>package main

import "fmt"
import "os"
import "strings"
import "regexp"
import "flag"
import "bufio"
import "log"
import "github.com/joncrlsn/pgutil"
import "github.com/joncrlsn/fileutil"
import "github.com/joncrlsn/misc"

// End of Statement regex
var eosRegex *regexp.Regexp = regexp.MustCompile(`;\s*$|;\s*--.*$`)
var inReader *bufio.Reader = bufio.NewReader(os.Stdin)

// Executes a file of SQL statements one statement at a time, stopping everything
// if one of them has an error
func main() {

    // -f (filename) is a required program argument
	var fileName string
	flag.StringVar(&fileName, "f", "", "path of the SQL file to run")
	dbInfo := pgutil.DbInfo{}
	dbInfo.DbOptions = "sslmode=disable"
	dbInfo.Populate()

	if len(fileName) == 0 {
        fmt.Fprintln(os.Stderr, "Missing required filename argument (-f)")
		usage()
	}

	exists, _ := fileutil.Exists(fileName)
	if !exists {
		fmt.Fprintf(os.Stderr, "File does not exist: %s\n", fileName)
		os.Exit(2)
	}

    runFile(fileName, &dbInfo)
}

// Reads the file and runs the SQL statements one by one
func runFile(fileName string, dbInfo *pgutil.DbInfo) {
    // Open connection to the database
    db, err := dbInfo.Open()
    check("opening database", err)

    // Read each statement from the file one at a time and execute them
	sqlChan := sqlStatements(fileName)
	for sql := range sqlChan {
		// Execute SQL.  If not successful, stop and ask user
		// whether or not we should continue
		fmt.Println("\n================================")
		log.Print("Executing SQL: ", sql)
        result, err := db.Exec(sql)

        // If there was an error, ask user whether or not we should continue
		if err != nil {
            fmt.Fprintln(os.Stderr, "Error: ", err)
            if misc.PromptYesNo("SQL failed!  Do you want to continue?", false)  {
                continue
            }
            os.Exit(1)
		}

        rowCnt, err := result.RowsAffected()
        check("getting rows affected count", err)
        log.Printf("Rows affected: %d\n", rowCnt)
	}
    log.Println("Done!")
}

/*
 * Reads and returns (via channel) SQL statements from the given file.
 * SQL statements must end with a semi-colon
 */
func sqlStatements(fileName string) <-chan string {
	statementChan := make(chan string)

	go func() {
		lineChan := fileutil.ReadLinesChannel(fileName)

		// TODO: Convert this to a string builder
		statement := ""
		for line := range lineChan {
			//fmt.Printf("  Line: %s\n", line)

			// ignore blank or empty lines
			line = strings.TrimSpace(line)
			if len(line) == 0 {
				continue
			}
			if strings.HasPrefix(line, "--") {
				continue
			}

			statement += line + "\n"

			// look for line ending with just a semi-colon
			// or a semi-colon with a SQL comment following
			if eosRegex.MatchString(line) {
				statementChan <- statement
				statement = ""
			}
		}

		close(statementChan)
	}()

	return statementChan
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s -f <sqlFileName> [-host <string>] [-port <int>] [-db <string>] [-user <string>] [-pw <password>] \n", os.Args[0])
    fmt.Fprintln(os.Stderr, `
Database connection properties can be specified in two ways:
  * Environment variables
  * Program flags (override environment variables)
  * ~/.pgpass file (for the password)

Environment variables are:
  DBHOST : host name where database is running (default is localhost)
  DBPORT : port database is listening on (default is 5432)
  DBNAME : name of database you want to update
  DBUSER : user in postgres you'll be executing the commands as
  DBPASS : password for the user

Program flags are:
  -f     : required. file path to the SQL
  -U     : user in postgres to execute the commands (matches psql flag)
  -h     : host name where database is running--default is localhost (matches psql flag)
  -p     : port.  defaults to 5432 (matches psql flag)
  -d     : database name (matches psql flag)
  -pw    : password for the postgres user`)
	os.Exit(2)
}

func check(msg string, err error) {
    if err != nil {
        log.Fatal("Error " + msg, err)
    }
}
<|endoftext|>


<commit_before>package piwik

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"

	"gopkg.in/macaron.v1"
)

type Options struct {
	PiwikUrl         string
	IgnoreDoNotTrack bool
	WebsiteID        string
	Token            string
}

func prepareOptions(options []Options) Options {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}
	opt.PiwikUrl = strings.TrimSuffix(strings.TrimSuffix(opt.PiwikUrl, "piwik.php"), "/") + "/piwik.php?"

	return opt
}

func Piwik(options ...Options) macaron.Handler {
	opt := prepareOptions(options)

	return func(ctx *macaron.Context, logger *log.Logger) {
		params := make(url.Values)
		params.Set("idsite", opt.WebsiteID)
		params.Set("rec", "1")

		params.Set("url", ctx.Req.URL.String())
		params.Set("apiv", "1")

		h := ctx.Req.Header
		params.Set("urlref", h.Get("Referer"))
		fmt.Println("USERAGENT")
		fmt.Println(h.Get("Accept-Language"))
		params.Set("ua", h.Get("User-Agent"))
		params.Set("lang", h.Get("Accept-Language"))

		params.Set("token_auth", opt.Token)
		params.Set("cip", ctx.RemoteAddr())

		// collecting data is finished, go async now
		go func() {
			logger.Println(params.Encode())
			res, err := http.Get(opt.PiwikUrl + params.Encode())
			if err != nil {
				logger.Println("Error contacting piwik:", err)
			}
			r, err := ioutil.ReadAll(res.Body)
			logger.Println(string(r))
		}()

		ctx.Next()
	}
}
<commit_msg>added DNT<commit_after>package piwik

import (
	"log"
	"net/http"
	"net/url"
	"strings"

	"gopkg.in/macaron.v1"
)

type Options struct {
	PiwikUrl         string
	IgnoreDoNotTrack bool
	WebsiteID        string
	Token            string
}

func prepareOptions(options []Options) Options {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}
	opt.PiwikUrl = strings.TrimSuffix(strings.TrimSuffix(opt.PiwikUrl, "piwik.php"), "/") + "/piwik.php?"

	return opt
}

func Piwik(options ...Options) macaron.Handler {
	opt := prepareOptions(options)

	return func(ctx *macaron.Context, logger *log.Logger) {
		if !opt.IgnoreDoNotTrack && ctx.Req.Header.Get("DNT") == "1" {
			return
		}

		params := make(url.Values)
		params.Set("idsite", opt.WebsiteID)
		params.Set("rec", "1")

		params.Set("url", ctx.Req.URL.String())
		params.Set("apiv", "1")

		h := ctx.Req.Header
		params.Set("urlref", h.Get("Referer"))
		params.Set("ua", h.Get("User-Agent"))
		params.Set("lang", h.Get("Accept-Language"))

		params.Set("token_auth", opt.Token)
		params.Set("cip", ctx.RemoteAddr())

		// collecting data is finished, go async now
		go func() {
			res, err := http.Get(opt.PiwikUrl + params.Encode())
			if err != nil {
				logger.Println("Error contacting piwik:", err)
			}
			if res.StatusCode != http.StatusOK {
				logger.Println("Error contacting piwik:", res.Status)
			}
		}()

		ctx.Next()
	}
}
<|endoftext|>


<commit_before>// This file was auto-generated using createmock. See the following page for
// more information:
//
//     https://github.com/jacobsa/oglemock
//

package mock_backup

import (
	fmt "fmt"
	backup "github.com/jacobsa/comeback/backup"
	blob "github.com/jacobsa/comeback/blob"
	oglemock "github.com/jacobsa/oglemock"
	regexp "regexp"
	runtime "runtime"
	unsafe "unsafe"
)

type MockDirectorySaver interface {
	backup.DirectorySaver
	oglemock.MockObject
}

type mockDirectorySaver struct {
	controller  oglemock.Controller
	description string
}

func NewMockDirectorySaver(
	c oglemock.Controller,
	desc string) MockDirectorySaver {
	return &mockDirectorySaver{
		controller:  c,
		description: desc,
	}
}

func (m *mockDirectorySaver) Oglemock_Id() uintptr {
	return uintptr(unsafe.Pointer(m))
}

func (m *mockDirectorySaver) Oglemock_Description() string {
	return m.description
}

func (m *mockDirectorySaver) Save(p0 string, p1 string, p2 []regexp.Regexp) (o0 blob.Score, o1 error) {
	// Get a file name and line number for the caller.
	_, file, line, _ := runtime.Caller(1)

	// Hand the call off to the controller, which does most of the work.
	retVals := m.controller.HandleMethodCall(
		m,
		"Save",
		file,
		line,
		[]interface{}{p0, p1, p2})

	if len(retVals) != 2 {
		panic(fmt.Sprintf("mockDirectorySaver.Save: invalid return values: %v", retVals))
	}

	// o0 blob.Score
	if retVals[0] != nil {
		o0 = retVals[0].(blob.Score)
	}

	// o1 error
	if retVals[1] != nil {
		o1 = retVals[1].(error)
	}

	return
}
<commit_msg>Updated mock again.<commit_after>// This file was auto-generated using createmock. See the following page for
// more information:
//
//     https://github.com/jacobsa/oglemock
//

package mock_backup

import (
	backup "github.com/jacobsa/comeback/backup"
	blob "github.com/jacobsa/comeback/blob"
	fmt "fmt"
	oglemock "github.com/jacobsa/oglemock"
	regexp "regexp"
	runtime "runtime"
	unsafe "unsafe"
)

type MockDirectorySaver interface {
	backup.DirectorySaver
	oglemock.MockObject
}

type mockDirectorySaver struct {
	controller	oglemock.Controller
	description	string
}

func NewMockDirectorySaver(
	c oglemock.Controller,
	desc string) MockDirectorySaver {
	return &mockDirectorySaver{
		controller:	c,
		description:	desc,
	}
}

func (m *mockDirectorySaver) Oglemock_Id() uintptr {
	return uintptr(unsafe.Pointer(m))
}

func (m *mockDirectorySaver) Oglemock_Description() string {
	return m.description
}

func (m *mockDirectorySaver) Save(p0 string, p1 string, p2 []*regexp.Regexp) (o0 blob.Score, o1 error) {
	// Get a file name and line number for the caller.
	_, file, line, _ := runtime.Caller(1)

	// Hand the call off to the controller, which does most of the work.
	retVals := m.controller.HandleMethodCall(
		m,
		"Save",
		file,
		line,
		[]interface{}{p0, p1, p2})

	if len(retVals) != 2 {
		panic(fmt.Sprintf("mockDirectorySaver.Save: invalid return values: %v", retVals))
	}

	// o0 blob.Score
	if retVals[0] != nil {
		o0 = retVals[0].(blob.Score)
	}

	// o1 error
	if retVals[1] != nil {
		o1 = retVals[1].(error)
	}

	return
}
<|endoftext|>


<commit_before>package jobs

import (
	"sort"

	"github.com/aerogo/aero"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

var jobInfo = map[string]*utils.JobInfo{
	"anime-ratings": &utils.JobInfo{
		Name: "anime-ratings",
	},
	"twist": &utils.JobInfo{
		Name: "twist",
	},
	"refresh-osu": &utils.JobInfo{
		Name: "refresh-osu",
	},
	"mal-download": &utils.JobInfo{
		Name: "mal-download",
	},
	"mal-parse": &utils.JobInfo{
		Name: "mal-parse",
	},
	// "mal-sync": &utils.JobInfo{
	// 	Name: "mal-sync",
	// },
}

var jobLogs = []string{}

// Overview shows all background jobs.
func Overview(ctx *aero.Context) string {
	user := utils.GetUser(ctx)
	jobs := []*utils.JobInfo{}

	for _, job := range jobInfo {
		jobs = append(jobs, job)
	}

	sort.Slice(jobs, func(i, j int) bool {
		return jobs[i].Name < jobs[j].Name
	})

	return ctx.HTML(components.EditorJobs(jobs, jobLogs, ctx.URI(), user))
}
<commit_msg>Added Kitsu anime import to editor accessible jobs<commit_after>package jobs

import (
	"sort"

	"github.com/aerogo/aero"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

var jobInfo = map[string]*utils.JobInfo{
	"anime-ratings": &utils.JobInfo{
		Name: "anime-ratings",
	},
	"kitsu-import-anime": &utils.JobInfo{
		Name: "kitsu-import-anime",
	},
	"twist": &utils.JobInfo{
		Name: "twist",
	},
	"refresh-osu": &utils.JobInfo{
		Name: "refresh-osu",
	},
	"mal-download": &utils.JobInfo{
		Name: "mal-download",
	},
	"mal-parse": &utils.JobInfo{
		Name: "mal-parse",
	},
	// "mal-sync": &utils.JobInfo{
	// 	Name: "mal-sync",
	// },
}

var jobLogs = []string{}

// Overview shows all background jobs.
func Overview(ctx *aero.Context) string {
	user := utils.GetUser(ctx)
	jobs := []*utils.JobInfo{}

	for _, job := range jobInfo {
		jobs = append(jobs, job)
	}

	sort.Slice(jobs, func(i, j int) bool {
		return jobs[i].Name < jobs[j].Name
	})

	return ctx.HTML(components.EditorJobs(jobs, jobLogs, ctx.URI(), user))
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rsa

// This file implements the PSS signature scheme [1].
//
// [1] http://www.rsa.com/rsalabs/pkcs/files/h11300-wp-pkcs-1v2-2-rsa-cryptography-standard.pdf

import (
	"bytes"
	"crypto"
	"errors"
	"hash"
	"io"
	"math/big"
)

func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error) {
	// See [1], section 9.1.1
	hLen := hash.Size()
	sLen := len(salt)
	emLen := (emBits + 7) / 8

	// 1.  If the length of M is greater than the input limitation for the
	//     hash function (2^61 - 1 octets for SHA-1), output "message too
	//     long" and stop.
	//
	// 2.  Let mHash = Hash(M), an octet string of length hLen.

	if len(mHash) != hLen {
		return nil, errors.New("crypto/rsa: input must be hashed message")
	}

	// 3.  If emLen < hLen + sLen + 2, output "encoding error" and stop.

	if emLen < hLen+sLen+2 {
		return nil, errors.New("crypto/rsa: encoding error")
	}

	em := make([]byte, emLen)
	db := em[:emLen-sLen-hLen-2+1+sLen]
	h := em[emLen-sLen-hLen-2+1+sLen : emLen-1]

	// 4.  Generate a random octet string salt of length sLen; if sLen = 0,
	//     then salt is the empty string.
	//
	// 5.  Let
	//       M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt;
	//
	//     M' is an octet string of length 8 + hLen + sLen with eight
	//     initial zero octets.
	//
	// 6.  Let H = Hash(M'), an octet string of length hLen.

	var prefix [8]byte

	hash.Write(prefix[:])
	hash.Write(mHash)
	hash.Write(salt)

	h = hash.Sum(h[:0])
	hash.Reset()

	// 7.  Generate an octet string PS consisting of emLen - sLen - hLen - 2
	//     zero octets. The length of PS may be 0.
	//
	// 8.  Let DB = PS || 0x01 || salt; DB is an octet string of length
	//     emLen - hLen - 1.

	db[emLen-sLen-hLen-2] = 0x01
	copy(db[emLen-sLen-hLen-1:], salt)

	// 9.  Let dbMask = MGF(H, emLen - hLen - 1).
	//
	// 10. Let maskedDB = DB \xor dbMask.

	mgf1XOR(db, hash, h)

	// 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in
	//     maskedDB to zero.

	db[0] &= (0xFF >> uint(8*emLen-emBits))

	// 12. Let EM = maskedDB || H || 0xbc.
	em[emLen-1] = 0xBC

	// 13. Output EM.
	return em, nil
}

func emsaPSSVerify(mHash, em []byte, emBits, sLen int, hash hash.Hash) error {
	// 1.  If the length of M is greater than the input limitation for the
	//     hash function (2^61 - 1 octets for SHA-1), output "inconsistent"
	//     and stop.
	//
	// 2.  Let mHash = Hash(M), an octet string of length hLen.
	hLen := hash.Size()
	if hLen != len(mHash) {
		return ErrVerification
	}

	// 3.  If emLen < hLen + sLen + 2, output "inconsistent" and stop.
	emLen := (emBits + 7) / 8
	if emLen < hLen+sLen+2 {
		return ErrVerification
	}

	// 4.  If the rightmost octet of EM does not have hexadecimal value
	//     0xbc, output "inconsistent" and stop.
	if em[len(em)-1] != 0xBC {
		return ErrVerification
	}

	// 5.  Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
	//     let H be the next hLen octets.
	db := em[:emLen-hLen-1]
	h := em[emLen-hLen-1 : len(em)-1]

	// 6.  If the leftmost 8 * emLen - emBits bits of the leftmost octet in
	//     maskedDB are not all equal to zero, output "inconsistent" and
	//     stop.
	if em[0]&(0xFF<<uint(8-(8*emLen-emBits))) != 0 {
		return ErrVerification
	}

	// 7.  Let dbMask = MGF(H, emLen - hLen - 1).
	//
	// 8.  Let DB = maskedDB \xor dbMask.
	mgf1XOR(db, hash, h)

	// 9.  Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB
	//     to zero.
	db[0] &= (0xFF >> uint(8*emLen-emBits))

	if sLen == PSSSaltLengthAuto {
	FindSaltLength:
		for sLen = emLen - (hLen + 2); sLen >= 0; sLen-- {
			switch db[emLen-hLen-sLen-2] {
			case 1:
				break FindSaltLength
			case 0:
				continue
			default:
				return ErrVerification
			}
		}
		if sLen < 0 {
			return ErrVerification
		}
	} else {
		// 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
		//     or if the octet at position emLen - hLen - sLen - 1 (the leftmost
		//     position is "position 1") does not have hexadecimal value 0x01,
		//     output "inconsistent" and stop.
		for _, e := range db[:emLen-hLen-sLen-2] {
			if e != 0x00 {
				return ErrVerification
			}
		}
		if db[emLen-hLen-sLen-2] != 0x01 {
			return ErrVerification
		}
	}

	// 11.  Let salt be the last sLen octets of DB.
	salt := db[len(db)-sLen:]

	// 12.  Let
	//          M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
	//     M' is an octet string of length 8 + hLen + sLen with eight
	//     initial zero octets.
	//
	// 13. Let H' = Hash(M'), an octet string of length hLen.
	var prefix [8]byte
	hash.Write(prefix[:])
	hash.Write(mHash)
	hash.Write(salt)

	h0 := hash.Sum(nil)

	// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
	if !bytes.Equal(h0, h) {
		return ErrVerification
	}
	return nil
}

// signPSSWithSalt calculates the signature of hashed using PSS [1] with specified salt.
// Note that hashed must be the result of hashing the input message using the
// given hash function. salt is a random sequence of bytes whose length will be
// later used to verify the signature.
func signPSSWithSalt(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) (s []byte, err error) {
	nBits := priv.N.BitLen()
	em, err := emsaPSSEncode(hashed, nBits-1, salt, hash.New())
	if err != nil {
		return
	}
	m := new(big.Int).SetBytes(em)
	c, err := decryptAndCheck(rand, priv, m)
	if err != nil {
		return
	}
	s = make([]byte, (nBits+7)/8)
	copyWithLeftPad(s, c.Bytes())
	return
}

const (
	// PSSSaltLengthAuto causes the salt in a PSS signature to be as large
	// as possible when signing, and to be auto-detected when verifying.
	PSSSaltLengthAuto = 0
	// PSSSaltLengthEqualsHash causes the salt length to equal the length
	// of the hash used in the signature.
	PSSSaltLengthEqualsHash = -1
)

// PSSOptions contains options for creating and verifying PSS signatures.
type PSSOptions struct {
	// SaltLength controls the length of the salt used in the PSS
	// signature. It can either be a number of bytes, or one of the special
	// PSSSaltLength constants.
	SaltLength int

	// Hash, if not zero, overrides the hash function passed to SignPSS.
	// This is the only way to specify the hash function when using the
	// crypto.Signer interface.
	Hash crypto.Hash
}

// HashFunc returns pssOpts.Hash so that PSSOptions implements
// crypto.SignerOpts.
func (pssOpts *PSSOptions) HashFunc() crypto.Hash {
	return pssOpts.Hash
}

func (opts *PSSOptions) saltLength() int {
	if opts == nil {
		return PSSSaltLengthAuto
	}
	return opts.SaltLength
}

// SignPSS calculates the signature of hashed using RSASSA-PSS [1].
// Note that hashed must be the result of hashing the input message using the
// given hash function. The opts argument may be nil, in which case sensible
// defaults are used.
func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte, opts *PSSOptions) ([]byte, error) {
	saltLength := opts.saltLength()
	switch saltLength {
	case PSSSaltLengthAuto:
		saltLength = (priv.N.BitLen()+7)/8 - 2 - hash.Size()
	case PSSSaltLengthEqualsHash:
		saltLength = hash.Size()
	}

	if opts != nil && opts.Hash != 0 {
		hash = opts.Hash
	}

	salt := make([]byte, saltLength)
	if _, err := io.ReadFull(rand, salt); err != nil {
		return nil, err
	}
	return signPSSWithSalt(rand, priv, hash, hashed, salt)
}

// VerifyPSS verifies a PSS signature.
// hashed is the result of hashing the input message using the given hash
// function and sig is the signature. A valid signature is indicated by
// returning a nil error. The opts argument may be nil, in which case sensible
// defaults are used.
func VerifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, opts *PSSOptions) error {
	return verifyPSS(pub, hash, hashed, sig, opts.saltLength())
}

// verifyPSS verifies a PSS signature with the given salt length.
func verifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, saltLen int) error {
	nBits := pub.N.BitLen()
	if len(sig) != (nBits+7)/8 {
		return ErrVerification
	}
	s := new(big.Int).SetBytes(sig)
	m := encrypt(new(big.Int), pub, s)
	emBits := nBits - 1
	emLen := (emBits + 7) / 8
	if emLen < len(m.Bytes()) {
		return ErrVerification
	}
	em := make([]byte, emLen)
	copyWithLeftPad(em, m.Bytes())
	if saltLen == PSSSaltLengthEqualsHash {
		saltLen = hash.Size()
	}
	return emsaPSSVerify(hashed, em, emBits, saltLen, hash.New())
}
<commit_msg>crypto/rsa: fix URL for the PKCS #1 v2.2 document in pss.go<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rsa

// This file implements the PSS signature scheme [1].
//
// [1] https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf

import (
	"bytes"
	"crypto"
	"errors"
	"hash"
	"io"
	"math/big"
)

func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error) {
	// See [1], section 9.1.1
	hLen := hash.Size()
	sLen := len(salt)
	emLen := (emBits + 7) / 8

	// 1.  If the length of M is greater than the input limitation for the
	//     hash function (2^61 - 1 octets for SHA-1), output "message too
	//     long" and stop.
	//
	// 2.  Let mHash = Hash(M), an octet string of length hLen.

	if len(mHash) != hLen {
		return nil, errors.New("crypto/rsa: input must be hashed message")
	}

	// 3.  If emLen < hLen + sLen + 2, output "encoding error" and stop.

	if emLen < hLen+sLen+2 {
		return nil, errors.New("crypto/rsa: encoding error")
	}

	em := make([]byte, emLen)
	db := em[:emLen-sLen-hLen-2+1+sLen]
	h := em[emLen-sLen-hLen-2+1+sLen : emLen-1]

	// 4.  Generate a random octet string salt of length sLen; if sLen = 0,
	//     then salt is the empty string.
	//
	// 5.  Let
	//       M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt;
	//
	//     M' is an octet string of length 8 + hLen + sLen with eight
	//     initial zero octets.
	//
	// 6.  Let H = Hash(M'), an octet string of length hLen.

	var prefix [8]byte

	hash.Write(prefix[:])
	hash.Write(mHash)
	hash.Write(salt)

	h = hash.Sum(h[:0])
	hash.Reset()

	// 7.  Generate an octet string PS consisting of emLen - sLen - hLen - 2
	//     zero octets. The length of PS may be 0.
	//
	// 8.  Let DB = PS || 0x01 || salt; DB is an octet string of length
	//     emLen - hLen - 1.

	db[emLen-sLen-hLen-2] = 0x01
	copy(db[emLen-sLen-hLen-1:], salt)

	// 9.  Let dbMask = MGF(H, emLen - hLen - 1).
	//
	// 10. Let maskedDB = DB \xor dbMask.

	mgf1XOR(db, hash, h)

	// 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in
	//     maskedDB to zero.

	db[0] &= (0xFF >> uint(8*emLen-emBits))

	// 12. Let EM = maskedDB || H || 0xbc.
	em[emLen-1] = 0xBC

	// 13. Output EM.
	return em, nil
}

func emsaPSSVerify(mHash, em []byte, emBits, sLen int, hash hash.Hash) error {
	// 1.  If the length of M is greater than the input limitation for the
	//     hash function (2^61 - 1 octets for SHA-1), output "inconsistent"
	//     and stop.
	//
	// 2.  Let mHash = Hash(M), an octet string of length hLen.
	hLen := hash.Size()
	if hLen != len(mHash) {
		return ErrVerification
	}

	// 3.  If emLen < hLen + sLen + 2, output "inconsistent" and stop.
	emLen := (emBits + 7) / 8
	if emLen < hLen+sLen+2 {
		return ErrVerification
	}

	// 4.  If the rightmost octet of EM does not have hexadecimal value
	//     0xbc, output "inconsistent" and stop.
	if em[len(em)-1] != 0xBC {
		return ErrVerification
	}

	// 5.  Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
	//     let H be the next hLen octets.
	db := em[:emLen-hLen-1]
	h := em[emLen-hLen-1 : len(em)-1]

	// 6.  If the leftmost 8 * emLen - emBits bits of the leftmost octet in
	//     maskedDB are not all equal to zero, output "inconsistent" and
	//     stop.
	if em[0]&(0xFF<<uint(8-(8*emLen-emBits))) != 0 {
		return ErrVerification
	}

	// 7.  Let dbMask = MGF(H, emLen - hLen - 1).
	//
	// 8.  Let DB = maskedDB \xor dbMask.
	mgf1XOR(db, hash, h)

	// 9.  Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB
	//     to zero.
	db[0] &= (0xFF >> uint(8*emLen-emBits))

	if sLen == PSSSaltLengthAuto {
	FindSaltLength:
		for sLen = emLen - (hLen + 2); sLen >= 0; sLen-- {
			switch db[emLen-hLen-sLen-2] {
			case 1:
				break FindSaltLength
			case 0:
				continue
			default:
				return ErrVerification
			}
		}
		if sLen < 0 {
			return ErrVerification
		}
	} else {
		// 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
		//     or if the octet at position emLen - hLen - sLen - 1 (the leftmost
		//     position is "position 1") does not have hexadecimal value 0x01,
		//     output "inconsistent" and stop.
		for _, e := range db[:emLen-hLen-sLen-2] {
			if e != 0x00 {
				return ErrVerification
			}
		}
		if db[emLen-hLen-sLen-2] != 0x01 {
			return ErrVerification
		}
	}

	// 11.  Let salt be the last sLen octets of DB.
	salt := db[len(db)-sLen:]

	// 12.  Let
	//          M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ;
	//     M' is an octet string of length 8 + hLen + sLen with eight
	//     initial zero octets.
	//
	// 13. Let H' = Hash(M'), an octet string of length hLen.
	var prefix [8]byte
	hash.Write(prefix[:])
	hash.Write(mHash)
	hash.Write(salt)

	h0 := hash.Sum(nil)

	// 14. If H = H', output "consistent." Otherwise, output "inconsistent."
	if !bytes.Equal(h0, h) {
		return ErrVerification
	}
	return nil
}

// signPSSWithSalt calculates the signature of hashed using PSS [1] with specified salt.
// Note that hashed must be the result of hashing the input message using the
// given hash function. salt is a random sequence of bytes whose length will be
// later used to verify the signature.
func signPSSWithSalt(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) (s []byte, err error) {
	nBits := priv.N.BitLen()
	em, err := emsaPSSEncode(hashed, nBits-1, salt, hash.New())
	if err != nil {
		return
	}
	m := new(big.Int).SetBytes(em)
	c, err := decryptAndCheck(rand, priv, m)
	if err != nil {
		return
	}
	s = make([]byte, (nBits+7)/8)
	copyWithLeftPad(s, c.Bytes())
	return
}

const (
	// PSSSaltLengthAuto causes the salt in a PSS signature to be as large
	// as possible when signing, and to be auto-detected when verifying.
	PSSSaltLengthAuto = 0
	// PSSSaltLengthEqualsHash causes the salt length to equal the length
	// of the hash used in the signature.
	PSSSaltLengthEqualsHash = -1
)

// PSSOptions contains options for creating and verifying PSS signatures.
type PSSOptions struct {
	// SaltLength controls the length of the salt used in the PSS
	// signature. It can either be a number of bytes, or one of the special
	// PSSSaltLength constants.
	SaltLength int

	// Hash, if not zero, overrides the hash function passed to SignPSS.
	// This is the only way to specify the hash function when using the
	// crypto.Signer interface.
	Hash crypto.Hash
}

// HashFunc returns pssOpts.Hash so that PSSOptions implements
// crypto.SignerOpts.
func (pssOpts *PSSOptions) HashFunc() crypto.Hash {
	return pssOpts.Hash
}

func (opts *PSSOptions) saltLength() int {
	if opts == nil {
		return PSSSaltLengthAuto
	}
	return opts.SaltLength
}

// SignPSS calculates the signature of hashed using RSASSA-PSS [1].
// Note that hashed must be the result of hashing the input message using the
// given hash function. The opts argument may be nil, in which case sensible
// defaults are used.
func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte, opts *PSSOptions) ([]byte, error) {
	saltLength := opts.saltLength()
	switch saltLength {
	case PSSSaltLengthAuto:
		saltLength = (priv.N.BitLen()+7)/8 - 2 - hash.Size()
	case PSSSaltLengthEqualsHash:
		saltLength = hash.Size()
	}

	if opts != nil && opts.Hash != 0 {
		hash = opts.Hash
	}

	salt := make([]byte, saltLength)
	if _, err := io.ReadFull(rand, salt); err != nil {
		return nil, err
	}
	return signPSSWithSalt(rand, priv, hash, hashed, salt)
}

// VerifyPSS verifies a PSS signature.
// hashed is the result of hashing the input message using the given hash
// function and sig is the signature. A valid signature is indicated by
// returning a nil error. The opts argument may be nil, in which case sensible
// defaults are used.
func VerifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, opts *PSSOptions) error {
	return verifyPSS(pub, hash, hashed, sig, opts.saltLength())
}

// verifyPSS verifies a PSS signature with the given salt length.
func verifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, saltLen int) error {
	nBits := pub.N.BitLen()
	if len(sig) != (nBits+7)/8 {
		return ErrVerification
	}
	s := new(big.Int).SetBytes(sig)
	m := encrypt(new(big.Int), pub, s)
	emBits := nBits - 1
	emLen := (emBits + 7) / 8
	if emLen < len(m.Bytes()) {
		return ErrVerification
	}
	em := make([]byte, emLen)
	copyWithLeftPad(em, m.Bytes())
	if saltLen == PSSSaltLengthEqualsHash {
		saltLen = hash.Size()
	}
	return emsaPSSVerify(hashed, em, emBits, saltLen, hash.New())
}
<|endoftext|>