/*
 * Copyright (C) 2014 ~ 2018 Deepin Technology Co., Ltd.
 *
 * Author:     jouyouyun <jouyouwen717@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package shadow

// #include <shadow.h>
import "C"

import (
	"fmt"
)

// spwd wraps up `spwd` (shadow password) used in kernel.
type Shadow struct {
	Name       string // login username
	Password   string // encrypted password
	LastChange int64  // Date of last change since 1970-01-01
	MinDays    int64  // Min # of days between changes
	MaxDays    int64  // Max # of days between changes
	Warn       int64  // # of dayd before password expires to warn user to change it
	Inactive   int64  // # of days after password expires until account is disabled
	Expire     int64  // Date when account expires since 1970-01-01
	Flag       uint64 // reserved
}

// shadowC2Go converts `spwd` struct to golang native struct
func shadowC2Go(shadowC *C.struct_spwd) *Shadow {
	return &Shadow{
		Name:       C.GoString(shadowC.sp_namp),
		Password:   C.GoString(shadowC.sp_pwdp),
		LastChange: int64(shadowC.sp_lstchg),
		MinDays:    int64(shadowC.sp_min),
		MaxDays:    int64(shadowC.sp_max),
		Warn:       int64(shadowC.sp_warn),
		Inactive:   int64(shadowC.sp_inact),
		Expire:     int64(shadowC.sp_expire),
		Flag:       uint64(shadowC.sp_flag),
	}
}

type UserNotFoundError struct {
	Name string
	Uid  uint32
}

func (err *UserNotFoundError) Error() string {
	if len(err.Name) > 0 {
		return fmt.Sprintf("User with name `%s` not found!", err.Name)
	} else {
		return fmt.Sprintf("User with uid `%d` not found!", err.Uid)
	}
}

// GetShadowByName wraps up `getspnam` system call.
// It returns the record in the shadow database file that matches the username
// @name
func GetShadowByName(name string) (*Shadow, error) {
	nameC := C.CString(name)
	shadowC, err := C.getspnam(nameC)
	if shadowC == nil {
		if err == nil {
			return nil, &UserNotFoundError{Name: name}
		} else {
			return nil, err
		}
	} else {
		return shadowC2Go(shadowC), nil
	}
}

// GetShadowEntry wraups up `getspent` system call.
// It performs sequential scans of the records in the shadow file.
func GetShadowEntry() []*Shadow {
	shadows := make([]*Shadow, 0)

	// Restart scanning from the begging of the shadow file.
	C.setspent()

	for shadowC, err := C.getspent(); shadowC != nil && err == nil; shadowC, err = C.getspent() {
		shadow := shadowC2Go(shadowC)
		shadows = append(shadows, shadow)
	}

	// Call endspent() is necessary so that any subsequent getspent() call will
	// reopen the shadow file and start from the beginning.
	C.endspent()

	return shadows
}
