instead of hardcoding it to a potential outdated value. For `smbclient` we only set max-protocol version and that could only be smb2 or smb3 (no finer granularity) any how, so this was not really correct. Nowadays the kernel dropped SMB1 and tries to go for SMB2.1 or higher by default, depending on what client and server supports. SMB2.1 is Windows 7/2008R2 - both EOL since quite a bit, so ok as default lower boundary. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
299 lines
7.1 KiB
Perl
299 lines
7.1 KiB
Perl
package PVE::Storage::CIFSPlugin;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Net::IP;
|
|
use PVE::Tools qw(run_command);
|
|
use PVE::ProcFSTools;
|
|
use File::Path;
|
|
use PVE::Storage::Plugin;
|
|
use PVE::JSONSchema qw(get_standard_option);
|
|
|
|
use base qw(PVE::Storage::Plugin);
|
|
|
|
# CIFS helper functions
|
|
|
|
sub cifs_is_mounted {
|
|
my ($server, $share, $mountpoint, $mountdata) = @_;
|
|
|
|
$server = "[$server]" if Net::IP::ip_is_ipv6($server);
|
|
my $source = "//${server}/$share";
|
|
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
|
|
|
|
return $mountpoint if grep {
|
|
$_->[2] =~ /^cifs/ &&
|
|
$_->[0] =~ m|^\Q$source\E/?$| &&
|
|
$_->[1] eq $mountpoint
|
|
} @$mountdata;
|
|
return undef;
|
|
}
|
|
|
|
sub cifs_cred_file_name {
|
|
my ($storeid) = @_;
|
|
return "/etc/pve/priv/storage/${storeid}.pw";
|
|
}
|
|
|
|
sub cifs_delete_credentials {
|
|
my ($storeid) = @_;
|
|
|
|
if (my $cred_file = get_cred_file($storeid)) {
|
|
unlink($cred_file) or warn "removing cifs credientials '$cred_file' failed: $!\n";
|
|
}
|
|
}
|
|
|
|
sub cifs_set_credentials {
|
|
my ($password, $storeid) = @_;
|
|
|
|
my $cred_file = cifs_cred_file_name($storeid);
|
|
mkdir "/etc/pve/priv/storage";
|
|
|
|
PVE::Tools::file_set_contents($cred_file, "password=$password\n");
|
|
|
|
return $cred_file;
|
|
}
|
|
|
|
sub get_cred_file {
|
|
my ($storeid) = @_;
|
|
|
|
my $cred_file = cifs_cred_file_name($storeid);
|
|
|
|
if (-e $cred_file) {
|
|
return $cred_file;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
sub cifs_mount {
|
|
my ($server, $share, $mountpoint, $storeid, $smbver, $user, $domain) = @_;
|
|
|
|
$server = "[$server]" if Net::IP::ip_is_ipv6($server);
|
|
my $source = "//${server}/$share";
|
|
|
|
my $cmd = ['/bin/mount', '-t', 'cifs', $source, $mountpoint, '-o', 'soft', '-o'];
|
|
|
|
if (my $cred_file = get_cred_file($storeid)) {
|
|
push @$cmd, "username=$user", '-o', "credentials=$cred_file";
|
|
push @$cmd, '-o', "domain=$domain" if defined($domain);
|
|
} else {
|
|
push @$cmd, 'guest,username=guest';
|
|
}
|
|
|
|
push @$cmd, '-o', defined($smbver) ? "vers=$smbver" : "vers=default";
|
|
|
|
run_command($cmd, errmsg => "mount error");
|
|
}
|
|
|
|
# Configuration
|
|
|
|
sub type {
|
|
return 'cifs';
|
|
}
|
|
|
|
sub plugindata {
|
|
return {
|
|
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1,
|
|
backup => 1, snippets => 1}, { images => 1 }],
|
|
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
|
|
};
|
|
}
|
|
|
|
sub properties {
|
|
return {
|
|
share => {
|
|
description => "CIFS share.",
|
|
type => 'string',
|
|
},
|
|
password => {
|
|
description => "Password for accessing the share/datastore.",
|
|
type => 'string',
|
|
maxLength => 256,
|
|
},
|
|
domain => {
|
|
description => "CIFS domain.",
|
|
type => 'string',
|
|
optional => 1,
|
|
maxLength => 256,
|
|
},
|
|
smbversion => {
|
|
description => "SMB protocol version. 'default' if not set, negotiates the highest SMB2+"
|
|
." version supported by both the client and server.",
|
|
type => 'string',
|
|
default => 'default',
|
|
enum => ['default', '2.0', '2.1', '3', '3.0', '3.11'],
|
|
optional => 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
sub options {
|
|
return {
|
|
path => { fixed => 1 },
|
|
server => { fixed => 1 },
|
|
share => { fixed => 1 },
|
|
nodes => { optional => 1 },
|
|
disable => { optional => 1 },
|
|
maxfiles => { optional => 1 },
|
|
'prune-backups' => { optional => 1 },
|
|
content => { optional => 1 },
|
|
format => { optional => 1 },
|
|
username => { optional => 1 },
|
|
password => { optional => 1},
|
|
domain => { optional => 1},
|
|
smbversion => { optional => 1},
|
|
mkdir => { optional => 1 },
|
|
bwlimit => { optional => 1 },
|
|
};
|
|
}
|
|
|
|
|
|
sub check_config {
|
|
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
|
|
|
|
$config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};
|
|
|
|
return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
|
|
}
|
|
|
|
# Storage implementation
|
|
|
|
sub on_add_hook {
|
|
my ($class, $storeid, $scfg, %sensitive) = @_;
|
|
|
|
if (defined($sensitive{password})) {
|
|
cifs_set_credentials($sensitive{password}, $storeid);
|
|
if (!exists($scfg->{username})) {
|
|
warn "storage $storeid: ignoring password parameter, no user set\n";
|
|
}
|
|
} else {
|
|
cifs_delete_credentials($storeid);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub on_update_hook {
|
|
my ($class, $storeid, $scfg, %sensitive) = @_;
|
|
|
|
return if !exists($sensitive{password});
|
|
|
|
if (defined($sensitive{password})) {
|
|
cifs_set_credentials($sensitive{password}, $storeid);
|
|
if (!exists($scfg->{username})) {
|
|
warn "storage $storeid: ignoring password parameter, no user set\n";
|
|
}
|
|
} else {
|
|
cifs_delete_credentials($storeid);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub on_delete_hook {
|
|
my ($class, $storeid, $scfg) = @_;
|
|
|
|
cifs_delete_credentials($storeid);
|
|
|
|
return;
|
|
}
|
|
|
|
sub status {
|
|
my ($class, $storeid, $scfg, $cache) = @_;
|
|
|
|
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
|
if !$cache->{mountdata};
|
|
|
|
my $path = $scfg->{path};
|
|
my $server = $scfg->{server};
|
|
my $share = $scfg->{share};
|
|
|
|
return undef
|
|
if !cifs_is_mounted($server, $share, $path, $cache->{mountdata});
|
|
|
|
return $class->SUPER::status($storeid, $scfg, $cache);
|
|
}
|
|
|
|
sub activate_storage {
|
|
my ($class, $storeid, $scfg, $cache) = @_;
|
|
|
|
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
|
if !$cache->{mountdata};
|
|
|
|
my $path = $scfg->{path};
|
|
my $server = $scfg->{server};
|
|
my $share = $scfg->{share};
|
|
|
|
if (!cifs_is_mounted($server, $share, $path, $cache->{mountdata})) {
|
|
|
|
mkpath $path if !(defined($scfg->{mkdir}) && !$scfg->{mkdir});
|
|
|
|
die "unable to activate storage '$storeid' - " .
|
|
"directory '$path' does not exist\n" if ! -d $path;
|
|
|
|
cifs_mount($server, $share, $path, $storeid, $scfg->{smbversion},
|
|
$scfg->{username}, $scfg->{domain});
|
|
}
|
|
|
|
$class->SUPER::activate_storage($storeid, $scfg, $cache);
|
|
}
|
|
|
|
sub deactivate_storage {
|
|
my ($class, $storeid, $scfg, $cache) = @_;
|
|
|
|
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
|
if !$cache->{mountdata};
|
|
|
|
my $path = $scfg->{path};
|
|
my $server = $scfg->{server};
|
|
my $share = $scfg->{share};
|
|
|
|
if (cifs_is_mounted($server, $share, $path, $cache->{mountdata})) {
|
|
my $cmd = ['/bin/umount', $path];
|
|
run_command($cmd, errmsg => 'umount error');
|
|
}
|
|
}
|
|
|
|
sub check_connection {
|
|
my ($class, $storeid, $scfg) = @_;
|
|
|
|
my $servicename = '//'.$scfg->{server}.'/'.$scfg->{share};
|
|
|
|
my $cmd = ['/usr/bin/smbclient', $servicename, '-d', '0'];
|
|
|
|
if (defined($scfg->{smbversion}) && $scfg->{smbversion} ne 'default') {
|
|
# max-protocol version, so basically only relevant for smb2 vs smb3
|
|
push @$cmd, '-m', "smb" . int($scfg->{smbversion});
|
|
}
|
|
|
|
if (my $cred_file = get_cred_file($storeid)) {
|
|
push @$cmd, '-U', $scfg->{username}, '-A', $cred_file;
|
|
push @$cmd, '-W', $scfg->{domain} if defined($scfg->{domain});
|
|
} else {
|
|
push @$cmd, '-U', 'Guest','-N';
|
|
}
|
|
push @$cmd, '-c', 'echo 1 0';
|
|
|
|
my $out_str;
|
|
my $out = sub { $out_str .= shift };
|
|
|
|
eval { run_command($cmd, timeout => 10, outfunc => $out, errfunc => sub {}) };
|
|
|
|
if (my $err = $@) {
|
|
die "$out_str\n" if defined($out_str) &&
|
|
($out_str =~ m/NT_STATUS_ACCESS_DENIED/);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub get_volume_notes {
|
|
my $class = shift;
|
|
PVE::Storage::DirPlugin::get_volume_notes($class, @_);
|
|
}
|
|
sub update_volume_notes {
|
|
my $class = shift;
|
|
PVE::Storage::DirPlugin::update_volume_notes($class, @_);
|
|
}
|
|
|
|
1;
|