/* SSHSecure - a program to harden OpenSSH from defaults Copyright (C) 2020 Brent Saner 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 (at your option) 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 . */ package utils import ( "encoding/gob" "errors" "fmt" "os" gopath "path" "path/filepath" "time" "r00t2.io/sysutils/paths" "r00t2.io/sshsecure/sharedconsts" ) func backupWrite(path string, data []byte, system bool, force bool) error { var err error var uid int var gid int var running bool var runningInfo runInfo var curInfo runInfo uid, gid, err = getPerms(system) if err != nil { return err } if err = paths.RealPath(&path); err != nil { return err } curInfo = getInfo() parent := filepath.Dir(path) parentExists, err := paths.RealPathExists(&parent) infoFile := gopath.Join( parent, fmt.Sprintf(".%v.gob", filepath.Base(path), )) if err = paths.MakeDirIfNotExist(&parent); err != nil { return err } exists, err := paths.RealPathExists(&path) if err != nil { return err } else if !parentExists { // It exists *now*, but it's a new dir. We set perms thusly. os.Chmod(parent, 0700) } if gobExists, err := paths.RealPathExists(&infoFile); err != nil { return err } else if gobExists && !force { return nil // Don't go any further; the file was already generated. } running, runningInfo, err = isRunning() if err != nil { return err } if exists && running { e := fmt.Sprintf( "refusing to write to %v; another instance of program is currently running with PID %v started at %v", path, runningInfo.Pid, runningInfo.TimeStarted) return errors.New(e) } else if exists && !running { newName := fmt.Sprintf("%v.bak.%d", path, curInfo.TimeStarted.Unix()) if err = os.Rename(path, newName); err != nil { return err } } // FINALLY. The business end. w, err := os.Create(path) if err != nil { return err } gobFile, err := os.Create(infoFile) if err != nil { return err } g := gob.NewEncoder(gobFile) defer w.Close() defer gobFile.Close() if err = w.Chmod(0600); err != nil { return err } if err = w.Chown(uid, gid); err != nil { return err } if _, err = w.Write(data); err != nil { return err } if err = gobFile.Chmod(0600); err != nil { return err } if err = gobFile.Chown(uid, gid); err != nil { return err } if err = g.Encode(curInfo); err != nil { return err } return nil } func getInfo() runInfo { curInfo := runInfo{ TimeStarted: getTime(), Pid: getPid(), } return curInfo } func getPid() int { return os.Getpid() } func getPerms(system bool) (int, int, error) { var uid int var gid int if system { if os.Geteuid() != 0 { return 0, 0, errors.New("cannot create system file without root permissions") } uid = 0 gid = 0 } else { uid = os.Geteuid() gid = os.Getegid() } return uid, gid, nil } func getTime() time.Time { return time.Now() } func isRunning() (bool, runInfo, error) { curInfo := getInfo() var fileInfo runInfo lckFile := sharedconsts.LockFile exists, _ := paths.RealPathExists(&lckFile) if !exists { // This should never happen. If it does, I goofed the locking (or it's not invoked from a shell). return false, fileInfo, errors.New("*grabs shotgun* computer's haunted") } reader, err := os.Open(lckFile) if err != nil { return false, fileInfo, err } defer reader.Close() gobDecoder := gob.NewDecoder(reader) gobDecoder.Decode(&fileInfo) if fileInfo != curInfo { return true, fileInfo, nil } return false, fileInfo, nil }