mirror of
https://github.com/rwinkhart/libmutton.git
synced 2026-08-27 20:36:29 -04:00
Initial password aging support
This commit is contained in:
+83
-21
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rwinkhart/go-boilerplate/back"
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
@@ -15,13 +16,11 @@ const (
|
||||
AnsiDelete = "\033[38;5;1m"
|
||||
)
|
||||
|
||||
var RootLength = len(global.EntryRoot) // length of global.EntryRoot string
|
||||
|
||||
// GetModTimes returns a list of all entry modification times.
|
||||
func GetModTimes(entryList []string) []int64 {
|
||||
var modList []int64
|
||||
for _, file := range entryList {
|
||||
modTime, _ := os.Stat(global.TargetLocationFormat(file))
|
||||
modTime, _ := os.Stat(global.GetRealPath(file))
|
||||
modList = append(modList, modTime.ModTime().Unix())
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ func GetModTimes(entryList []string) []int64 {
|
||||
// isDir (only on client; for use in ShearRemoteFromClient).
|
||||
// If the local system is a server, it will also add the target to the deletions list for all clients (except the requesting client).
|
||||
// This function should only be used directly by the server binary.
|
||||
func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool, error) {
|
||||
func ShearLocal(vanityPath, clientDeviceID string, onlyShearAgingFile bool) (string, bool, error) {
|
||||
// determine if running on a server
|
||||
var onServer bool
|
||||
if clientDeviceID != "" {
|
||||
@@ -45,26 +44,35 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool,
|
||||
return "", false, errors.New("unable to generate device ID list: " + err.Error())
|
||||
}
|
||||
|
||||
// add the sheared target (incomplete, vanity) to the deletions list (if running on a server)
|
||||
// add the sheared vanityPath to the deletions list (if running on a server)
|
||||
if onServer {
|
||||
for _, device := range deviceIDList {
|
||||
if device.Name() != clientDeviceID {
|
||||
fileToClose, err := os.OpenFile(global.ConfigDir+global.PathSeparator+"deletions"+global.PathSeparator+device.Name()+global.FSSpace+strings.ReplaceAll(targetLocationIncomplete, "/", global.FSPath), os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if !onlyShearAgingFile {
|
||||
f, err := os.OpenFile(global.ConfigDir+global.PathSeparator+"deletions"+global.PathSeparator+device.Name()+global.FSSpace+"entry"+global.FSSpace+strings.ReplaceAll(vanityPath, "/", global.FSPath), os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
// do not print error as there is currently no way of seeing server-side errors
|
||||
// failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical)
|
||||
os.Exit(back.ErrorWrite)
|
||||
}
|
||||
_ = f.Close() // error ignored; if the file could be created, it can probably be closed
|
||||
}
|
||||
f, err := os.OpenFile(global.ConfigDir+global.PathSeparator+"deletions"+global.PathSeparator+device.Name()+global.FSSpace+"age"+global.FSSpace+strings.ReplaceAll(vanityPath, "/", global.FSPath), os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
// do not print error as there is currently no way of seeing server-side errors
|
||||
// failure to add the target to the deletions list will exit the program and result in a client re-uploading the target (non-critical)
|
||||
os.Exit(back.ErrorWrite)
|
||||
}
|
||||
_ = fileToClose.Close() // error ignored; if the file could be created, it can probably be closed
|
||||
_ = f.Close() // error ignored; if the file could be created, it can probably be closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the target locally
|
||||
targetLocationComplete := global.TargetLocationFormat(targetLocationIncomplete)
|
||||
realPath := global.GetRealPath(vanityPath)
|
||||
var isFile bool
|
||||
if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist
|
||||
isAccessible, err := back.TargetIsFile(targetLocationComplete, true)
|
||||
isAccessible, err := back.TargetIsFile(realPath, true)
|
||||
if !isAccessible {
|
||||
return "", false, err
|
||||
}
|
||||
@@ -72,9 +80,15 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool,
|
||||
isFile = true
|
||||
}
|
||||
}
|
||||
err = os.RemoveAll(targetLocationComplete)
|
||||
if !onlyShearAgingFile {
|
||||
err = os.RemoveAll(realPath)
|
||||
if err != nil {
|
||||
return "", false, errors.New("unable to remove local entry (" + vanityPath + "): " + err.Error())
|
||||
}
|
||||
}
|
||||
err = ShearAgeFileLocal(vanityPath)
|
||||
if err != nil {
|
||||
return "", false, errors.New("unable to remove local target: " + err.Error())
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if !onServer && len(deviceIDList) > 0 { // return the device ID if running on the client and a device ID exists (online mode)
|
||||
@@ -85,36 +99,84 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool,
|
||||
// do not exit program, as this function is used as part of ShearRemoteFromClient
|
||||
}
|
||||
|
||||
// ShearAgeFileLocal removes the age file for a vanity path.
|
||||
// This function should only be used directly by the server binary.
|
||||
func ShearAgeFileLocal(vanityPath string) error {
|
||||
err := os.RemoveAll(global.AgeDir + global.PathSeparator + strings.ReplaceAll(vanityPath, "/", global.FSPath))
|
||||
if err != nil {
|
||||
return errors.New("unable to remove age file for " + vanityPath + ": " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEntryAges reads the aging directory and returns a
|
||||
// map of vanity paths to their corresponding age timestamps.
|
||||
func GetEntryAges() (map[string]int64, error) {
|
||||
contents, err := os.ReadDir(global.AgeDir)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to read aging directory contents: " + err.Error())
|
||||
}
|
||||
|
||||
var vanityPathsToTimestamps = make(map[string]int64)
|
||||
for _, dirEntry := range contents {
|
||||
if !dirEntry.IsDir() {
|
||||
vanityPath := strings.ReplaceAll(dirEntry.Name(), global.FSPath, "/")
|
||||
info, err := dirEntry.Info()
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to read aging file modtime for " + vanityPath + ": " + err.Error())
|
||||
}
|
||||
vanityPathsToTimestamps[vanityPath] = info.ModTime().Unix()
|
||||
}
|
||||
}
|
||||
|
||||
return vanityPathsToTimestamps, nil
|
||||
}
|
||||
|
||||
// RenameLocal renames oldLocationIncomplete to newLocationIncomplete on the local system.
|
||||
// This function should only be used directly by the server binary.
|
||||
func RenameLocal(oldLocationIncomplete, newLocationIncomplete string) error {
|
||||
func RenameLocal(oldVanityPath, newVanityPath string) error {
|
||||
// get full paths for both locations
|
||||
oldLocation := global.TargetLocationFormat(oldLocationIncomplete)
|
||||
newLocation := global.TargetLocationFormat(newLocationIncomplete)
|
||||
oldRealPath := global.GetRealPath(oldVanityPath)
|
||||
oldRealAgePath := global.GetRealAgePath(oldVanityPath)
|
||||
newRealPath := global.GetRealPath(newVanityPath)
|
||||
newRealAgePath := global.GetRealAgePath(newVanityPath)
|
||||
|
||||
// ensure newLocation does not exist
|
||||
isAccessible, _ := back.TargetIsFile(newLocation, true) // error is ignored because dir/file status is irrelevant
|
||||
isAccessible, _ := back.TargetIsFile(newRealPath, true) // error is ignored because dir/file status is irrelevant
|
||||
if isAccessible {
|
||||
return errors.New("new target (" + newLocation + ") already exists")
|
||||
return errors.New("new target (" + newRealPath + ") already exists")
|
||||
}
|
||||
|
||||
// rename oldLocation to newLocation
|
||||
err := os.Rename(oldLocation, newLocation)
|
||||
err := os.Rename(oldRealPath, newRealPath)
|
||||
if err != nil {
|
||||
return errors.New("unable to rename: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
// do the same for the age file (if one exists) - also back up timestamp first
|
||||
var fileInfo os.FileInfo
|
||||
fileInfo, err = os.Stat(oldRealAgePath)
|
||||
if err == nil { // assume age file does not exist if os.Stat errors
|
||||
err = os.Rename(oldRealAgePath, newRealAgePath)
|
||||
if err != nil {
|
||||
return errors.New("unable to rename: " + err.Error())
|
||||
}
|
||||
err = os.Chtimes(newRealAgePath, time.Now(), fileInfo.ModTime())
|
||||
if err != nil {
|
||||
return errors.New("unable to set timestamp on age file for " + newVanityPath + ": " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// do not exit program, as this function is used as part of RenameRemoteFromClient
|
||||
}
|
||||
|
||||
// AddFolderLocal creates a new entry-containing directory on the local system.
|
||||
// This function should only be used directly by the server binary.
|
||||
func AddFolderLocal(targetLocationIncomplete string) error {
|
||||
func AddFolderLocal(vanityPath string) error {
|
||||
// create the target locally
|
||||
targetLocationComplete := global.TargetLocationFormat(targetLocationIncomplete)
|
||||
err := os.Mkdir(targetLocationComplete, 0700)
|
||||
realPath := global.GetRealPath(vanityPath)
|
||||
err := os.Mkdir(realPath, 0700)
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
fmt.Println(back.AnsiBlue + "Directory already exists - libmutton will still ensure it exists on the server")
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build windows
|
||||
|
||||
package synccommon
|
||||
|
||||
import (
|
||||
@@ -7,21 +5,20 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists).
|
||||
// WalkEntryDir walks the entry directory and returns lists of all files and
|
||||
// directories found (two separate lists) relative to the libmutton entry root.
|
||||
// Regardless of platform, all paths are stored with forward slashes (UNIX-style).
|
||||
func WalkEntryDir() ([]string, []string, error) {
|
||||
// define file/directory containing slices so that they may be accessed by the anonymous WalkDir function
|
||||
var fileList []string
|
||||
var dirList []string
|
||||
var fileList, dirList []string
|
||||
|
||||
// walk entry directory
|
||||
err := filepath.WalkDir(global.EntryRoot,
|
||||
func(fullPath string, entry fs.DirEntry, err error) error {
|
||||
func(realPath string, entry fs.DirEntry, err error) error {
|
||||
|
||||
// check for errors encountered while walking directory
|
||||
if err != nil {
|
||||
@@ -33,13 +30,13 @@ func WalkEntryDir() ([]string, []string, error) {
|
||||
}
|
||||
|
||||
// trim root path from each path before storing and replace backslashes with forward slashes
|
||||
trimmedPath := strings.ReplaceAll(fullPath[RootLength:], "\\", "/")
|
||||
vanityPath := global.GetVanityPath(realPath)
|
||||
|
||||
// append the path to the appropriate slice
|
||||
if !entry.IsDir() {
|
||||
fileList = append(fileList, trimmedPath)
|
||||
fileList = append(fileList, vanityPath)
|
||||
} else {
|
||||
dirList = append(dirList, trimmedPath)
|
||||
dirList = append(dirList, vanityPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1,50 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package synccommon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rwinkhart/libmutton/global"
|
||||
)
|
||||
|
||||
// WalkEntryDir walks the entry directory and returns lists of all files and directories found (two separate lists).
|
||||
// Regardless of platform, all paths are stored with forward slashes (UNIX-style).
|
||||
func WalkEntryDir() ([]string, []string, error) {
|
||||
// define file/directory containing slices so that they may be accessed by the anonymous WalkDir function
|
||||
var fileList []string
|
||||
var dirList []string
|
||||
|
||||
// walk entry directory
|
||||
err := filepath.WalkDir(global.EntryRoot,
|
||||
func(fullPath string, entry fs.DirEntry, err error) error {
|
||||
|
||||
// check for errors encountered while walking directory
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("entry directory does not exist; initialize libmutton to create it")
|
||||
} else {
|
||||
return errors.New("an unexpected error occurred while generating the entry list: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// trim root path from each path before storing
|
||||
trimmedPath := fullPath[RootLength:]
|
||||
|
||||
// append the path to the appropriate slice
|
||||
if !entry.IsDir() {
|
||||
fileList = append(fileList, trimmedPath)
|
||||
} else {
|
||||
dirList = append(dirList, trimmedPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return fileList, dirList, nil
|
||||
}
|
||||
Reference in New Issue
Block a user