// Copyright 2013 Jonas mg
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Passwords
//
// If the passwd field contains some string that is not a valid result of
// hashing, for instance "!" or "*", the user will not be able to use a unix
// passwd to log in (but the user may log in the system by other means).
//
// A passwd field which starts with a exclamation mark means that the passwd is
// locked. The remaining characters on the line represent the passwd field before
// the passwd was locked.

package user

import (
	"bufio"
	"errors"
	"io"
	"log"
	"os"

	"github.com/tredoe/osutil/user/crypt"
	_ "github.com/tredoe/osutil/user/crypt/md5_crypt"
	_ "github.com/tredoe/osutil/user/crypt/sha256_crypt"
	_ "github.com/tredoe/osutil/user/crypt/sha512_crypt"
	//_ "github.com/tredoe/osutil/user/crypt/bcrypt"
)

const _LOCK_CHAR = '!' // Character added at the beginning of the passwd to lock it.

var ErrShadowPasswd = errors.New("no found user with shadowed passwd")

// lookupCrypter returns the first crypt function found in shadowed passwd file.
func lookupCrypter() (crypt.Crypter, error) {
	f, err := os.Open(_SHADOW_FILE)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	buf := bufio.NewReader(f)

	for {
		line, _, err := buf.ReadLine()
		if err != nil {
			if err == io.EOF {
				return nil, ErrShadowPasswd
			}
			log.Print(err)
			continue
		}

		shadow, err := parseShadow(string(line))
		if err != nil {
			log.Print(err)
			continue
		}
		if shadow.password[0] == '$' {
			return crypt.NewFromHash(shadow.password), nil
		}
	}
	return nil, ErrShadowPasswd
}

// SetCrypter sets the crypt function to can hash the passwords.
// The type "crypt.Crypt" comes from package "github.com/tredoe/osutil/user/crypt".
func SetCrypter(c crypt.Crypt) {
	loadConfig()
	config.crypter = crypt.New(c)
}

// Passwd sets a hashed passwd for the actual user.
// The passwd must be supplied in clear-text.
func (s *Shadow) Passwd(key []byte) {
	loadConfig()
	s.password, _ = config.crypter.Generate(key, nil)
	s.setChange()
}

// Passwd sets a hashed passwd for the actual group.
// The passwd must be supplied in clear-text.
func (gs *GShadow) Passwd(key []byte) {
	loadConfig()
	gs.password, _ = config.crypter.Generate(key, nil)
}

// == Change passwd

// ChPasswd updates passwd.
// The passwd must be supplied in clear-text.
func ChPasswd(user string, key []byte) error {
	shadow, err := LookupShadow(user)
	if err != nil {
		return err
	}
	shadow.Passwd(key)

	return edit(user, shadow)
}

// ChGPasswd updates group passwd.
// The passwd must be supplied in clear-text.
func ChGPasswd(group string, key []byte) error {
	gshadow, err := LookupGShadow(group)
	if err != nil {
		return err
	}
	gshadow.Passwd(key)

	return edit(group, gshadow)
}

// == Locking

// LockUser locks the passwd of the given user.
func LockUser(name string) error {
	shadow, err := LookupShadow(name)
	if err != nil {
		return err
	}

	if shadow.password[0] != _LOCK_CHAR {
		shadow.password = string(_LOCK_CHAR) + shadow.password
		return edit(name, shadow)
	}
	return nil
}

// UnlockUser unlocks the passwd of the given user.
func UnlockUser(name string) error {
	shadow, err := LookupShadow(name)
	if err != nil {
		return err
	}

	if shadow.password[0] == _LOCK_CHAR {
		shadow.password = shadow.password[1:]
		return edit(name, shadow)
	}
	return nil
}