diff --git a/contrib/sudoers/osx b/contrib/sudoers/osx index d1e1d6606..fc0c12c0d 100644 --- a/contrib/sudoers/osx +++ b/contrib/sudoers/osx @@ -4,4 +4,6 @@ Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /usr/bin/sed -E -e /*/ d -ibak /etc/exports Cmnd_Alias VAGRANT_SMB_ADD = /usr/sbin/sharing -a * -S * -s * -g * -n * Cmnd_Alias VAGRANT_SMB_REMOVE = /usr/sbin/sharing -r * Cmnd_Alias VAGRANT_SMB_LIST = /usr/sbin/sharing -l -%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE, VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST +Cmnd_Alias VAGRANT_SMB_PSTART = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist +Cmnd_Alias VAGRANT_SMB_DSTART = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist +%admin ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD, VAGRANT_EXPORTS_REMOVE, VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST, VAGRANT_SMB_PSTART, VAGRANT_SMB_DSTART \ No newline at end of file diff --git a/plugins/hosts/darwin/cap/smb.rb b/plugins/hosts/darwin/cap/smb.rb index 263d2e4c8..8581921f4 100644 --- a/plugins/hosts/darwin/cap/smb.rb +++ b/plugins/hosts/darwin/cap/smb.rb @@ -10,6 +10,40 @@ module VagrantPlugins File.exist?("/usr/sbin/sharing") end + # Check if the required SMB services are loaded and enabled. If they are + # not, then start them up + def self.smb_start(env) + result = Vagrant::Util::Subprocess.execute("pwpolicy", "gethashtypes") + if result.exit_code == 0 && !result.stdout.include?("SMB-NT") + @@logger.error("SMB compatible password has not been stored") + raise SyncedFolderSMB::Errors::SMBCredentialsMissing + end + result = Vagrant::Util::Subprocess.execute("launchctl", "list", "com.apple.smb.preferences") + if result.exit_code != 0 + @@logger.warn("smb preferences service not enabled. enabling and starting...") + cmd = ["/bin/launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.smb.preferences.plist"] + result = Vagrant::Util::Subprocess.execute("/usr/bin/sudo", *cmd) + if result.exit_code != 0 + raise SyncedFolderSMB::Errors::SMBStartFailed, + command: cmd.join(" "), + stderr: result.stderr, + stdout: result.stdout + end + end + result = Vagrant::Util::Subprocess.execute("launchctl", "list", "com.apple.smbd") + if result.exit_code != 0 + @@logger.warn("smbd service not enabled. enabling and starting...") + cmd = ["/bin/launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.smbd.plist"] + result = Vagrant::Util::Subprocess.execute("/usr/bin/sudo", *cmd) + if result.exit_code != 0 + raise SyncedFolderSMB::Errors::SMBStartFailed, + command: cmd.join(" "), + stderr: result.stderr, + stdout: result.stdout + end + end + end + # Required options for mounting a share hosted # on macos. def self.smb_mount_options(env) diff --git a/plugins/hosts/darwin/plugin.rb b/plugins/hosts/darwin/plugin.rb index 92cb51787..7b08af3f1 100644 --- a/plugins/hosts/darwin/plugin.rb +++ b/plugins/hosts/darwin/plugin.rb @@ -41,6 +41,11 @@ module VagrantPlugins Cap::SMB end + host_capability("darwin", "smb_start") do + require_relative "cap/smb" + Cap::SMB + end + host_capability("darwin", "configured_ip_addresses") do require_relative "cap/configured_ip_addresses" Cap::ConfiguredIPAddresses diff --git a/plugins/synced_folders/smb/errors.rb b/plugins/synced_folders/smb/errors.rb index 14704b9bb..ebb9bbef3 100644 --- a/plugins/synced_folders/smb/errors.rb +++ b/plugins/synced_folders/smb/errors.rb @@ -10,6 +10,14 @@ module VagrantPlugins error_key(:not_supported) end + class SMBStartFailed < SMBError + error_key(:start_failed) + end + + class SMBCredentialsMissing < SMBError + error_key(:credentials_missing) + end + class DefineShareFailed < SMBError error_key(:define_share_failed) end diff --git a/plugins/synced_folders/smb/synced_folder.rb b/plugins/synced_folders/smb/synced_folder.rb index 29a60982a..426def425 100644 --- a/plugins/synced_folders/smb/synced_folder.rb +++ b/plugins/synced_folders/smb/synced_folder.rb @@ -30,6 +30,11 @@ module VagrantPlugins def prepare(machine, folders, opts) machine.ui.output(I18n.t("vagrant_sf_smb.preparing")) + # Check if this host can start and SMB service + if machine.env.host.capability?(:smb_start) + machine.env.host.capability(:smb_start) + end + smb_username = smb_password = nil # If we need auth information, then ask the user. diff --git a/templates/locales/synced_folder_smb.yml b/templates/locales/synced_folder_smb.yml index 143a4885f..79eb93aef 100644 --- a/templates/locales/synced_folder_smb.yml +++ b/templates/locales/synced_folder_smb.yml @@ -1,9 +1,10 @@ en: vagrant_sf_smb: not_supported: |- - It appears your machine doesn't support SMB, or there is not an - adapter to enable SMB on this machine for Vagrant. Ensure SMB - host functionality is available on this machine and try again. + It appears your machine doesn't support SMB, has not been + properly configured for SMB, or there is not an adapter to + enable SMB on this machine for Vagrant. Ensure SMB host + functionality is available on this machine and try again. mounting: |- Mounting SMB shared folders... mounting_single: |- @@ -23,6 +24,19 @@ en: Vagrant requires administator access to create SMB shares and may request access to complete setup of configured shares. errors: + start_failed: |- + Vagrant failed to automatically start the SMB service. Ensure the + required services can be started and try again. + + Command: %{command} + + Stderr: %{stderr} + + Stdout: %{stdout} + credentials_missing: |- + Vagrant SMB synced folders require the account password to be stored + in an NT compatible format. Please update your sharing settings to + enable a Windows compatible password and try again. define_share_failed: |- Exporting an SMB share failed! Details about the failure are shown below. Please inspect the error message and correct any problems. diff --git a/test/unit/plugins/hosts/darwin/cap/smb_test.rb b/test/unit/plugins/hosts/darwin/cap/smb_test.rb index dbe653978..a0ea44bcd 100644 --- a/test/unit/plugins/hosts/darwin/cap/smb_test.rb +++ b/test/unit/plugins/hosts/darwin/cap/smb_test.rb @@ -3,6 +3,8 @@ require_relative "../../../../base" require_relative "../../../../../../plugins/hosts/darwin/cap/smb" describe VagrantPlugins::HostDarwin::Cap::SMB do + include_context "unit" + let(:subject){ VagrantPlugins::HostDarwin::Cap::SMB } let(:machine){ double(:machine) } let(:env){ double(:env) } @@ -23,18 +25,61 @@ describe VagrantPlugins::HostDarwin::Cap::SMB do end end + describe ".smb_start" do + before{ allow(Vagrant::Util::Subprocess).to receive(:execute) + .and_return(result.new(0, "SMB-NT", "")) } + + it "should check for NT compatible password" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("pwpolicy", "gethashtypes"). + and_return(result.new(0, "SMB-NT", "")) + subject.smb_start(env) + end + + it "should raise error if NT compatible password is not set" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("pwpolicy", "gethashtypes"). + and_return(result.new(0, "", "")) + expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBCredentialsMissing) + end + + it "should ignore if the command returns non-zero" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("pwpolicy", "gethashtypes"). + and_return(result.new(1, "", "")) + subject.smb_start(env) + end + + it "should not load smb preferences if it is already loaded" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /preferences/).and_return(result.new(0, "", "")) + expect(Vagrant::Util::Subprocess).not_to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /preferences/) + subject.smb_start(env) + end + + it "should load smb preferences if it is not already loaded" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /preferences/).and_return(result.new(1, "", "")) + expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /preferences/).and_return(result.new(0, "", "")) + subject.smb_start(env) + end + + it "should raise error if load smb preferences fails" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with("launchctl", "list", /preferences/).and_return(result.new(1, "", "")) + expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /launchctl/, "load", "-w", /preferences/).and_return(result.new(1, "", "")) + expect{ subject.smb_start(env) }.to raise_error(VagrantPlugins::SyncedFolderSMB::Errors::SMBStartFailed) + end + + # TODO Finish out last start coverage + end + describe ".smb_cleanup" do after{ subject.smb_cleanup(env, machine, options) } it "should search for shares with generated machine ID" do expect(Vagrant::Util::Subprocess).to receive(:execute).with( - anything, anything, /.*CUSTOM_ID.*/).and_return(result.new(0, "", "")) + "/usr/bin/sudo", /sharing/, "-l").and_return(result.new(0, "", "")) end it "should remove shares individually" do expect(Vagrant::Util::Subprocess).to receive(:execute). - with(anything, anything, /.*CUSTOM_ID.*/). - and_return(result.new(0, "vgt-CUSTOM_ID-1\nvgt-CUSTOM_ID-2\n", "")) + with("/usr/bin/sudo", /sharing/, "-l"). + and_return(result.new(0, "name: vgt-CUSTOM_ID-1\nname: vgt-CUSTOM_ID-2\n", "")) expect(Vagrant::Util::Subprocess).to receive(:execute).with(/sudo/, /sharing/, anything, /CUSTOM_ID/). twice.and_return(result.new(0, "", "")) end diff --git a/test/unit/plugins/synced_folders/smb/synced_folder_test.rb b/test/unit/plugins/synced_folders/smb/synced_folder_test.rb index a1d40d9ef..b01902df8 100644 --- a/test/unit/plugins/synced_folders/smb/synced_folder_test.rb +++ b/test/unit/plugins/synced_folders/smb/synced_folder_test.rb @@ -67,7 +67,7 @@ describe VagrantPlugins::SyncedFolderSMB::SyncedFolder do end describe ".prepare" do - let(:host_caps){ [:smb_prepare] } + let(:host_caps){ [:smb_start, :smb_prepare] } context "without credentials provided" do before do @@ -86,6 +86,11 @@ describe VagrantPlugins::SyncedFolderSMB::SyncedFolder do expect(folders['/second/path'][:smb_username]).to eq('username') expect(folders['/second/path'][:smb_password]).to eq('password') end + + it "should start the SMB service if capability is available" do + expect(host).to receive(:capability).with(:smb_install, any_args) + subject.prepare(machine, folders, options) + end end context "with credentials provided" do diff --git a/website/source/docs/synced-folders/smb.html.md b/website/source/docs/synced-folders/smb.html.md index 1b977db87..c85cf674d 100644 --- a/website/source/docs/synced-folders/smb.html.md +++ b/website/source/docs/synced-folders/smb.html.md @@ -56,7 +56,9 @@ without requiring a password each time: Cmnd_Alias VAGRANT_SMB_ADD = /usr/sbin/sharing -a * -S * -s * -g * -n * Cmnd_Alias VAGRANT_SMB_REMOVE = /usr/sbin/sharing -r * Cmnd_Alias VAGRANT_SMB_LIST = /usr/sbin/sharing -l -%admin ALL=(root) NOPASSWD: VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST +Cmnd_Alias VAGRANT_SMB_PSTART = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist +Cmnd_Alias VAGRANT_SMB_DSTART = /bin/launchctl load -w /System/Library/LaunchDaemons/com.apple.smb.preferences.plist +%admin ALL=(root) NOPASSWD: VAGRANT_SMB_ADD, VAGRANT_SMB_REMOVE, VAGRANT_SMB_LIST, VAGRANT_SMB_PSTART, VAGRANT_SMB_DSTART ``` ### Guests