2023-09-02 20:03:44 +01:00
// Linsk - A utility to access Linux-native file systems on non-Linux operating systems.
// Copyright (c) 2023 The Linsk Authors.
//
// 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 <https://www.gnu.org/licenses/>.
2023-08-25 15:12:19 +01:00
package vm
import (
"bytes"
2023-08-29 13:29:46 +01:00
"context"
2023-08-25 19:55:11 +01:00
"crypto/rand"
2023-08-25 16:54:58 +01:00
"fmt"
2023-08-25 19:55:11 +01:00
"log/slog"
2023-08-30 15:24:25 +01:00
"net"
2023-08-25 19:55:11 +01:00
"os"
2023-08-26 16:26:35 +01:00
"strings"
2023-08-25 19:55:11 +01:00
"sync"
"syscall"
2023-08-29 13:29:46 +01:00
"time"
2023-08-25 15:12:19 +01:00
2023-08-29 14:24:18 +01:00
"github.com/AlexSSD7/linsk/sshutil"
2023-08-26 09:16:52 +01:00
"github.com/AlexSSD7/linsk/utils"
2023-08-25 16:54:58 +01:00
"github.com/alessio/shellescape"
2023-08-25 15:12:19 +01:00
"github.com/pkg/errors"
2023-08-25 19:55:11 +01:00
"go.uber.org/multierr"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
2023-08-25 15:12:19 +01:00
)
type FileManager struct {
2023-08-25 19:55:11 +01:00
logger * slog . Logger
2023-08-27 13:44:57 +01:00
vm * VM
2023-08-25 15:12:19 +01:00
}
2023-08-27 13:44:57 +01:00
func NewFileManager ( logger * slog . Logger , vm * VM ) * FileManager {
2023-08-25 15:12:19 +01:00
return & FileManager {
2023-08-25 19:55:11 +01:00
logger : logger ,
2023-08-27 13:44:57 +01:00
vm : vm ,
2023-08-25 15:12:19 +01:00
}
}
func ( fm * FileManager ) Init ( ) error {
2023-08-27 13:44:57 +01:00
sc , err := fm . vm . DialSSH ( )
2023-08-25 15:12:19 +01:00
if err != nil {
return errors . Wrap ( err , "dial vm ssh" )
}
2023-08-25 19:55:11 +01:00
defer func ( ) { _ = sc . Close ( ) } ( )
2023-08-29 14:24:18 +01:00
_ , err = sshutil . RunSSHCmd ( fm . vm . ctx , sc , "vgchange -ay" )
2023-08-25 15:12:19 +01:00
if err != nil {
return errors . Wrap ( err , "run vgchange cmd" )
}
return nil
}
func ( fm * FileManager ) Lsblk ( ) ( [ ] byte , error ) {
2023-08-27 13:44:57 +01:00
sc , err := fm . vm . DialSSH ( )
2023-08-25 15:12:19 +01:00
if err != nil {
return nil , errors . Wrap ( err , "dial vm ssh" )
}
2023-09-01 13:50:31 +01:00
ret , err := sshutil . RunSSHCmd ( fm . vm . ctx , sc , "lsblk -o NAME,SIZE,FSTYPE,LABEL -e 7,11,2" )
2023-08-25 15:12:19 +01:00
if err != nil {
return nil , errors . Wrap ( err , "run lsblk" )
}
2023-08-29 14:24:18 +01:00
return ret , nil
2023-08-25 15:12:19 +01:00
}
2023-08-25 16:54:58 +01:00
type MountOptions struct {
FSType string
2023-08-25 19:55:11 +01:00
LUKS bool
}
const luksDMName = "cryptmnt"
func ( fm * FileManager ) luksOpen ( sc * ssh . Client , fullDevPath string ) error {
lg := fm . logger . With ( "vm-path" , fullDevPath )
2023-09-01 12:40:13 +01:00
return sshutil . NewSSHSessionWithDelayedTimeout ( fm . vm . ctx , time . Second * 15 , sc , func ( sess * ssh . Session , startTimeout func ( preTimeout func ( ) ) ) error {
2023-08-29 14:24:18 +01:00
stdinPipe , err := sess . StdinPipe ( )
if err != nil {
return errors . Wrap ( err , "create vm ssh session stdin pipe" )
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
stderrBuf := bytes . NewBuffer ( nil )
sess . Stderr = stderrBuf
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
err = sess . Start ( "cryptsetup luksOpen " + shellescape . Quote ( fullDevPath ) + " " + luksDMName )
if err != nil {
return errors . Wrap ( err , "start cryptsetup luksopen cmd" )
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
lg . Info ( "Attempting to open a LUKS device" )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
_ , err = os . Stderr . Write ( [ ] byte ( "Enter Password: " ) )
if err != nil {
return errors . Wrap ( err , "write prompt to stderr" )
}
2023-08-25 19:55:11 +01:00
2023-09-03 14:08:24 +01:00
pwd , err := term . ReadPassword ( int ( syscall . Stdin ) )
2023-08-29 14:24:18 +01:00
if err != nil {
return errors . Wrap ( err , "read luks password" )
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
fmt . Print ( "\n" )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
// We start the timeout countdown now only to avoid timing out
// while the user is entering the password, or shortly after that.
2023-09-01 12:40:13 +01:00
startTimeout ( func ( ) {
lg . Warn ( "LUKS open command timed out. If you are using large-memory key derivation function, try increasing the VM memory allocation using --vm-mem-alloc flag." )
} )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
var wErr error
var wWG sync . WaitGroup
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
wWG . Add ( 1 )
go func ( ) {
defer wWG . Done ( )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
_ , err := stdinPipe . Write ( pwd )
_ , err2 := stdinPipe . Write ( [ ] byte ( "\n" ) )
wErr = errors . Wrap ( multierr . Combine ( err , err2 ) , "write password to stdin" )
} ( )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
defer func ( ) {
// Clear the memory up for security.
{
for i := 0 ; i < len ( pwd ) ; i ++ {
pwd [ i ] = 0
}
2023-08-29 13:29:46 +01:00
2023-08-29 14:24:18 +01:00
// This is my paranoia.
_ , _ = rand . Read ( pwd )
2023-08-29 13:29:46 +01:00
_ , _ = rand . Read ( pwd )
}
2023-08-29 14:24:18 +01:00
} ( )
2023-08-29 13:29:46 +01:00
2023-08-29 14:24:18 +01:00
err = sess . Wait ( )
if err != nil {
if strings . Contains ( stderrBuf . String ( ) , "Not enough available memory to open a keyslot." ) {
2023-09-01 12:40:13 +01:00
fm . logger . Warn ( "Detected not enough memory to open a LUKS device, please allocate more memory using --vm-mem-alloc flag." )
2023-08-29 14:24:18 +01:00
}
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
return utils . WrapErrWithLog ( err , "wait for cryptsetup luksopen cmd to finish" , stderrBuf . String ( ) )
2023-08-29 10:59:50 +01:00
}
2023-08-29 14:24:18 +01:00
lg . Info ( "LUKS device opened successfully" )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
_ = stdinPipe . Close ( )
wWG . Wait ( )
2023-08-25 19:55:11 +01:00
2023-08-29 14:24:18 +01:00
return wErr
} )
2023-08-25 16:54:58 +01:00
}
func ( fm * FileManager ) Mount ( devName string , mo MountOptions ) error {
if devName == "" {
return fmt . Errorf ( "device name is empty" )
}
2023-09-01 16:29:01 +01:00
// It does allow "mapper/" prefix for mapped devices.
2023-08-25 16:54:58 +01:00
// This is to enable the support for LVM and LUKS.
if ! utils . ValidateDevName ( devName ) {
return fmt . Errorf ( "bad device name" )
}
2023-08-29 14:24:18 +01:00
// We're intentionally not calling filepath.Clean() as
// this causes unintended consequences when run on Windows.
// (Windows Go standard library treats the path as it's for
// Windows, but we're targeting a Linux VM.)
2023-08-28 11:35:57 +02:00
fullDevPath := "/dev/" + devName
2023-08-25 16:54:58 +01:00
if mo . FSType == "" {
return fmt . Errorf ( "fs type is empty" )
}
2023-08-27 13:44:57 +01:00
sc , err := fm . vm . DialSSH ( )
2023-08-25 16:54:58 +01:00
if err != nil {
return errors . Wrap ( err , "dial vm ssh" )
}
2023-08-25 19:55:11 +01:00
defer func ( ) { _ = sc . Close ( ) } ( )
if mo . LUKS {
err = fm . luksOpen ( sc , fullDevPath )
if err != nil {
return errors . Wrap ( err , "luks open" )
}
fullDevPath = "/dev/mapper/" + luksDMName
2023-08-25 16:54:58 +01:00
}
2023-08-29 14:24:18 +01:00
_ , err = sshutil . RunSSHCmd ( fm . vm . ctx , sc , "mount -t " + shellescape . Quote ( mo . FSType ) + " " + shellescape . Quote ( fullDevPath ) + " /mnt" )
2023-08-25 16:54:58 +01:00
if err != nil {
2023-08-29 14:24:18 +01:00
return errors . Wrap ( err , "run mount cmd" )
2023-08-25 16:54:58 +01:00
}
return nil
}
2023-08-26 16:26:35 +01:00
2023-08-30 15:24:25 +01:00
func ( fm * FileManager ) StartFTP ( pwd string , passivePortStart uint16 , passivePortCount uint16 , extIP net . IP ) error {
2023-08-29 10:00:12 +01:00
ftpdCfg := ` anonymous_enable = NO
local_enable = YES
write_enable = YES
local_umask = 022
chroot_local_user = YES
allow_writeable_chroot = YES
listen = YES
seccomp_sandbox = NO
pasv_min_port = ` + fmt.Sprint(passivePortStart) + `
pasv_max_port = ` + fmt.Sprint(passivePortStart+passivePortCount) + `
2023-08-30 15:24:25 +01:00
pasv_address = ` + extIP.String() + `
2023-08-29 10:00:12 +01:00
`
2023-09-02 12:07:30 +01:00
return fm . startGenericShare ( pwd , ftpdCfg , "/etc/vsftpd/vsftpd.conf" , "vsftpd" , sshutil . ChangeUnixPass )
2023-08-26 16:26:35 +01:00
}
2023-08-31 16:23:40 +01:00
func ( fm * FileManager ) StartSMB ( pwd string ) error {
sambaCfg := ` [ global ]
workgroup = WORKGROUP
dos charset = cp866
unix charset = utf - 8
2023-09-01 14:41:35 +01:00
client min protocol = SMB2
client max protocol = SMB3
2023-08-31 16:23:40 +01:00
read raw = yes
write raw = yes
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF = 131072 SO_SNDBUF = 131072
min receivefile size = 16384
use sendfile = true
aio read size = 16384
aio write size = 16384
server signing = no
2023-09-01 14:41:35 +01:00
2023-08-31 16:23:40 +01:00
[ linsk ]
browseable = yes
writeable = yes
path = / mnt
force user = linsk
force group = linsk
2023-09-01 15:15:40 +01:00
create mask = 0664
`
2023-09-02 12:07:30 +01:00
return fm . startGenericShare ( pwd , sambaCfg , "/etc/samba/smb.conf" , "samba" , sshutil . ChangeSambaPass )
}
2023-08-31 16:23:40 +01:00
2023-09-02 12:07:30 +01:00
func ( fm * FileManager ) StartAFP ( pwd string ) error {
afpCfg := ` [ Global ]
2023-08-31 16:23:40 +01:00
2023-09-02 12:07:30 +01:00
[ linsk ]
path = / mnt
file perm = 0664
directory perm = 0775
valid users = linsk
force user = linsk
force group = linsk
`
2023-08-31 16:23:40 +01:00
2023-09-02 12:07:30 +01:00
return fm . startGenericShare ( pwd , afpCfg , "/etc/afp.conf" , "netatalk" , sshutil . ChangeUnixPass )
2023-08-31 16:23:40 +01:00
}
2023-09-01 15:15:40 +01:00
2023-09-02 12:07:30 +01:00
func ( fm * FileManager ) startGenericShare ( pwd string , cfg string , cfgPath string , rcServiceName string , changePassFunc sshutil . ChangePassFunc ) error {
2023-09-01 15:15:40 +01:00
// This timeout is for the SCP client exclusively.
scpCtx , scpCtxCancel := context . WithTimeout ( fm . vm . ctx , time . Second * 5 )
defer scpCtxCancel ( )
scpClient , err := fm . vm . DialSCP ( )
if err != nil {
return errors . Wrap ( err , "dial scp" )
}
defer scpClient . Close ( )
2023-09-02 12:07:30 +01:00
err = scpClient . CopyFile ( scpCtx , strings . NewReader ( cfg ) , cfgPath , "0400" )
2023-09-01 15:15:40 +01:00
if err != nil {
2023-09-02 12:07:30 +01:00
return errors . Wrap ( err , "copy config file" )
2023-09-01 15:15:40 +01:00
}
scpClient . Close ( )
sc , err := fm . vm . DialSSH ( )
if err != nil {
return errors . Wrap ( err , "dial ssh" )
}
defer func ( ) { _ = sc . Close ( ) } ( )
2023-09-02 12:07:30 +01:00
_ , err = sshutil . RunSSHCmd ( fm . vm . ctx , sc , "rc-update add " + shellescape . Quote ( rcServiceName ) + " && rc-service " + shellescape . Quote ( rcServiceName ) + " start" )
2023-09-01 15:15:40 +01:00
if err != nil {
2023-09-02 12:07:30 +01:00
return errors . Wrap ( err , "add and start rc service" )
2023-09-01 15:15:40 +01:00
}
2023-09-02 12:07:30 +01:00
err = changePassFunc ( fm . vm . ctx , sc , "linsk" , pwd )
2023-09-01 15:15:40 +01:00
if err != nil {
2023-09-02 12:07:30 +01:00
return errors . Wrap ( err , "change pass" )
2023-09-01 15:15:40 +01:00
}
return nil
}