1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/chromeos/smb_client/smb_service.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/callback_helpers.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/task/post_task.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "base/time/default_tick_clock.h"
18 #include "base/unguessable_token.h"
19 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
20 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
21 #include "chrome/browser/chromeos/kerberos/kerberos_credentials_manager.h"
22 #include "chrome/browser/chromeos/kerberos/kerberos_credentials_manager_factory.h"
23 #include "chrome/browser/chromeos/profiles/profile_helper.h"
24 #include "chrome/browser/chromeos/smb_client/discovery/mdns_host_locator.h"
25 #include "chrome/browser/chromeos/smb_client/discovery/netbios_client.h"
26 #include "chrome/browser/chromeos/smb_client/discovery/netbios_host_locator.h"
27 #include "chrome/browser/chromeos/smb_client/smb_file_system.h"
28 #include "chrome/browser/chromeos/smb_client/smb_file_system_id.h"
29 #include "chrome/browser/chromeos/smb_client/smb_kerberos_credentials_updater.h"
30 #include "chrome/browser/chromeos/smb_client/smb_provider.h"
31 #include "chrome/browser/chromeos/smb_client/smb_service_helper.h"
32 #include "chrome/browser/chromeos/smb_client/smb_share_info.h"
33 #include "chrome/browser/chromeos/smb_client/smb_url.h"
34 #include "chrome/browser/platform_util.h"
35 #include "chrome/browser/ui/webui/chromeos/smb_shares/smb_credentials_dialog.h"
36 #include "chrome/common/chrome_features.h"
37 #include "chrome/common/pref_names.h"
38 #include "chromeos/dbus/dbus_thread_manager.h"
39 #include "chromeos/dbus/smb_provider_client.h"
40 #include "components/pref_registry/pref_registry_syncable.h"
41 #include "components/prefs/pref_service.h"
42 #include "content/public/browser/browser_context.h"
43 #include "content/public/browser/storage_partition.h"
44 #include "crypto/random.h"
45 #include "net/base/network_interfaces.h"
46 #include "url/url_util.h"
47
48 namespace chromeos {
49 namespace smb_client {
50
51 namespace {
52
53 const char kShareUrlKey[] = "share_url";
54 const char kModeKey[] = "mode";
55 const char kModeDropDownValue[] = "drop_down";
56 const char kModePreMountValue[] = "pre_mount";
57 const char kModeUnknownValue[] = "unknown";
58 const base::TimeDelta kHostDiscoveryInterval = base::TimeDelta::FromSeconds(60);
59 // -3 is chosen because -1 and -2 have special meaning in smbprovider.
60 const int32_t kInvalidMountId = -3;
61 // Maximum number of smbfs shares to be mounted at the same time, only enforced
62 // on user-initiated mount requests.
63 const size_t kMaxSmbFsShares = 16;
64 // Length of salt used to obfuscate stored password in smbfs.
65 const size_t kSaltLength = 16;
66 static_assert(kSaltLength >=
67 smbfs::mojom::CredentialStorageOptions::kMinSaltLength,
68 "Minimum salt length is "
69 "smbfs::mojom::CredentialStorageOptions::kMinSaltLength");
70
GetInterfaces()71 net::NetworkInterfaceList GetInterfaces() {
72 net::NetworkInterfaceList list;
73 if (!net::GetNetworkList(&list, net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) {
74 LOG(ERROR) << "GetInterfaces failed";
75 }
76 return list;
77 }
78
GetNetBiosClient(Profile * profile)79 std::unique_ptr<NetBiosClientInterface> GetNetBiosClient(Profile* profile) {
80 auto* network_context =
81 content::BrowserContext::GetDefaultStoragePartition(profile)
82 ->GetNetworkContext();
83 return std::make_unique<NetBiosClient>(network_context);
84 }
85
IsSmbFsEnabled()86 bool IsSmbFsEnabled() {
87 return base::FeatureList::IsEnabled(features::kSmbFs);
88 }
89
90 // Metric recording functions.
91
92 // This enum is used to define the buckets for an enumerated UMA histogram.
93 // Hence,
94 // (a) existing enumerated constants should never be deleted or reordered, and
95 // (b) new constants should only be appended at the end of the enumeration.
96 enum class AuthMethod {
97 kNoCredentials = 0,
98 kUsernameOnly = 1,
99 kUsernameAndPassword = 2,
100 kSSOKerberosAD = 3,
101 kSSOKerberosGaia = 4,
102 kMaxValue = kSSOKerberosGaia,
103 };
104
RecordMountResult(SmbMountResult result)105 void RecordMountResult(SmbMountResult result) {
106 DCHECK_LE(result, SmbMountResult::kMaxValue);
107 UMA_HISTOGRAM_ENUMERATION("NativeSmbFileShare.MountResult", result);
108 }
109
RecordRemountResult(SmbMountResult result)110 void RecordRemountResult(SmbMountResult result) {
111 DCHECK_LE(result, SmbMountResult::kMaxValue);
112 UMA_HISTOGRAM_ENUMERATION("NativeSmbFileShare.RemountResult", result);
113 }
114
RecordAuthenticationMethod(AuthMethod method)115 void RecordAuthenticationMethod(AuthMethod method) {
116 DCHECK_LE(method, AuthMethod::kMaxValue);
117 UMA_HISTOGRAM_ENUMERATION("NativeSmbFileShare.AuthenticationMethod", method);
118 }
119
MakeFdWithContents(const std::string & contents)120 base::ScopedFD MakeFdWithContents(const std::string& contents) {
121 const size_t content_size = contents.size();
122
123 base::ScopedFD read_fd, write_fd;
124 if (!base::CreatePipe(&read_fd, &write_fd, true /* non_blocking */)) {
125 LOG(ERROR) << "Unable to create pipe";
126 return {};
127 }
128 bool success =
129 base::WriteFileDescriptor(write_fd.get(),
130 reinterpret_cast<const char*>(&content_size),
131 sizeof(content_size)) &&
132 base::WriteFileDescriptor(write_fd.get(), contents.data(), content_size);
133 if (!success) {
134 PLOG(ERROR) << "Unable to write contents to pipe";
135 return {};
136 }
137 return read_fd;
138 }
139
140 } // namespace
141
142 bool SmbService::disable_share_discovery_for_testing_ = false;
143
SmbService(Profile * profile,std::unique_ptr<base::TickClock> tick_clock)144 SmbService::SmbService(Profile* profile,
145 std::unique_ptr<base::TickClock> tick_clock)
146 : provider_id_(ProviderId::CreateFromNativeId("smb")),
147 profile_(profile),
148 tick_clock_(std::move(tick_clock)),
149 registry_(profile) {
150 user_manager::User* user =
151 chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
152 DCHECK(user);
153
154 SmbProviderClient* client = GetSmbProviderClient();
155 if (!client) {
156 return;
157 }
158
159 if (user->IsActiveDirectoryUser()) {
160 const std::string& account_id_guid = user->GetAccountId().GetObjGuid();
161 SetupKerberos(account_id_guid);
162 return;
163 }
164
165 KerberosCredentialsManager* credentials_manager =
166 KerberosCredentialsManagerFactory::GetExisting(profile);
167 if (credentials_manager && credentials_manager->IsKerberosEnabled()) {
168 smb_credentials_updater_ = std::make_unique<SmbKerberosCredentialsUpdater>(
169 credentials_manager,
170 base::BindRepeating(&SmbService::UpdateKerberosCredentials,
171 AsWeakPtr()));
172 SetupKerberos(smb_credentials_updater_->active_account_name());
173 return;
174 }
175
176 // Post a task to complete setup. This is to allow unit tests to perform
177 // expectations setup after constructing an instance. It also mirrors the
178 // behaviour when Kerberos is being used.
179 base::ThreadTaskRunnerHandle::Get()->PostTask(
180 FROM_HERE, base::BindOnce(&SmbService::CompleteSetup, AsWeakPtr()));
181 }
182
~SmbService()183 SmbService::~SmbService() {
184 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
185 if (chromeos::PowerManagerClient::Get()) {
186 chromeos::PowerManagerClient::Get()->RemoveObserver(this);
187 }
188 }
189
Shutdown()190 void SmbService::Shutdown() {
191 // Unmount and destroy all smbfs instances explicitly before destruction,
192 // since SmbFsShare accesses KeyedServices on destruction.
193 smbfs_shares_.clear();
194 }
195
196 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)197 void SmbService::RegisterProfilePrefs(
198 user_prefs::PrefRegistrySyncable* registry) {
199 registry->RegisterBooleanPref(prefs::kNetworkFileSharesAllowed, true);
200 registry->RegisterBooleanPref(prefs::kNetBiosShareDiscoveryEnabled, true);
201 registry->RegisterBooleanPref(prefs::kNTLMShareAuthenticationEnabled, true);
202 registry->RegisterListPref(prefs::kNetworkFileSharesPreconfiguredShares);
203 registry->RegisterStringPref(prefs::kMostRecentlyUsedNetworkFileShareURL, "");
204 SmbPersistedShareRegistry::RegisterProfilePrefs(registry);
205 }
206
UnmountSmbFs(const base::FilePath & mount_path)207 void SmbService::UnmountSmbFs(const base::FilePath& mount_path) {
208 DCHECK(!mount_path.empty());
209
210 for (auto it = smbfs_shares_.begin(); it != smbfs_shares_.end(); ++it) {
211 SmbFsShare* share = it->second.get();
212 if (share->mount_path() == mount_path) {
213 if (share->options().save_restore_password) {
214 share->RemoveSavedCredentials(
215 base::BindOnce(&SmbService::OnSmbfsRemoveSavedCredentialsDone,
216 base::Unretained(this), it->first));
217 } else {
218 // If the password wasn't saved, there's nothing for smbfs to do.
219 OnSmbfsRemoveSavedCredentialsDone(it->first, true /* success */);
220 }
221 return;
222 }
223 }
224
225 LOG(WARNING) << "Smbfs mount path not found: " << mount_path;
226 }
227
OnSmbfsRemoveSavedCredentialsDone(const std::string & mount_id,bool success)228 void SmbService::OnSmbfsRemoveSavedCredentialsDone(const std::string& mount_id,
229 bool success) {
230 DCHECK(!mount_id.empty());
231
232 auto it = smbfs_shares_.find(mount_id);
233 if (it == smbfs_shares_.end()) {
234 LOG(WARNING) << "Smbfs mount id " << mount_id << " already deleted";
235 return;
236 }
237
238 // UnmountSmbFs() is called by an explicit unmount by the user. In this
239 // case, forget the share.
240 registry_.Delete(it->second->share_url());
241 smbfs_shares_.erase(it);
242 }
243
GetSmbFsShareForPath(const base::FilePath & path)244 SmbFsShare* SmbService::GetSmbFsShareForPath(const base::FilePath& path) {
245 DCHECK(!path.empty());
246 DCHECK(path.IsAbsolute());
247
248 for (const auto& entry : smbfs_shares_) {
249 const base::FilePath mount_path = entry.second->mount_path();
250 if (mount_path == path || mount_path.IsParent(path)) {
251 return entry.second.get();
252 }
253 }
254 return nullptr;
255 }
256
GatherSharesInNetwork(HostDiscoveryResponse discovery_callback,GatherSharesResponse shares_callback)257 void SmbService::GatherSharesInNetwork(HostDiscoveryResponse discovery_callback,
258 GatherSharesResponse shares_callback) {
259 auto preconfigured_shares = GetPreconfiguredSharePathsForDropdown();
260 if (!preconfigured_shares.empty()) {
261 shares_callback.Run(std::move(preconfigured_shares), false);
262 }
263 share_finder_->GatherSharesInNetwork(
264 std::move(discovery_callback),
265 base::BindOnce(
266 [](GatherSharesResponse shares_callback,
267 const std::vector<SmbUrl>& shares_gathered) {
268 std::move(shares_callback).Run(shares_gathered, true);
269 },
270 std::move(shares_callback)));
271 }
272
UpdateSharePath(int32_t mount_id,const std::string & share_path,StartReadDirIfSuccessfulCallback reply)273 void SmbService::UpdateSharePath(int32_t mount_id,
274 const std::string& share_path,
275 StartReadDirIfSuccessfulCallback reply) {
276 GetSmbProviderClient()->UpdateSharePath(
277 mount_id, share_path,
278 base::BindOnce(&SmbService::OnUpdateSharePathResponse, AsWeakPtr(),
279 mount_id, std::move(reply)));
280 }
281
OnUpdateSharePathResponse(int32_t mount_id,StartReadDirIfSuccessfulCallback reply,smbprovider::ErrorType error)282 void SmbService::OnUpdateSharePathResponse(
283 int32_t mount_id,
284 StartReadDirIfSuccessfulCallback reply,
285 smbprovider::ErrorType error) {
286 if (error != smbprovider::ERROR_OK) {
287 LOG(ERROR) << "Failed to update the share path for mount id " << mount_id;
288 std::move(reply).Run(false /* should_retry_start_read_dir */);
289 return;
290 }
291 std::move(reply).Run(true /* should_retry_start_read_dir */);
292 }
293
Mount(const file_system_provider::MountOptions & options,const base::FilePath & share_path,const std::string & username_input,const std::string & password_input,bool use_kerberos,bool should_open_file_manager_after_mount,bool save_credentials,MountResponse callback)294 void SmbService::Mount(const file_system_provider::MountOptions& options,
295 const base::FilePath& share_path,
296 const std::string& username_input,
297 const std::string& password_input,
298 bool use_kerberos,
299 bool should_open_file_manager_after_mount,
300 bool save_credentials,
301 MountResponse callback) {
302 SmbUrl parsed_url(share_path.value());
303 if (!parsed_url.IsValid() || parsed_url.GetShare().empty()) {
304 // Handle invalid URLs early to avoid having unaccounted for UMA counts for
305 // authentication method.
306 std::move(callback).Run(SmbMountResult::kInvalidUrl);
307 return;
308 }
309
310 // When using kerberos, the URL must contain the hostname because that is used
311 // to obtain the ticket. If the user enters an IP address, Samba will give us
312 // a permission error, which isn't correct or useful to the end user.
313 if (use_kerberos && url::HostIsIPAddress(parsed_url.GetHost())) {
314 std::move(callback).Run(SmbMountResult::kInvalidSsoUrl);
315 return;
316 }
317
318 if (IsShareMounted(parsed_url)) {
319 // Prevent a share from being mounted twice. Although technically possible,
320 // the UX when doing so is incomplete.
321 std::move(callback).Run(SmbMountResult::kMountExists);
322 return;
323 }
324
325 if (IsSmbFsEnabled() && smbfs_shares_.size() >= kMaxSmbFsShares) {
326 // Prevent users from mounting an excessive number of shares.
327 std::move(callback).Run(SmbMountResult::kTooManyOpened);
328 return;
329 }
330
331 std::string username;
332 std::string password;
333 std::string workgroup;
334
335 user_manager::User* user =
336 chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
337 DCHECK(user);
338
339 if (use_kerberos) {
340 // Differentiate between AD and KerberosEnabled via policy in metrics.
341 if (IsKerberosEnabledViaPolicy()) {
342 RecordAuthenticationMethod(AuthMethod::kSSOKerberosGaia);
343 } else {
344 RecordAuthenticationMethod(AuthMethod::kSSOKerberosAD);
345 }
346
347 // Get the user's username and workgroup from their email address to be used
348 // for Kerberos authentication.
349 ParseUserPrincipalName(user->GetDisplayEmail(), &username, &workgroup);
350 } else {
351 // Record authentication method metrics.
352 if (!username_input.empty() && !password_input.empty()) {
353 RecordAuthenticationMethod(AuthMethod::kUsernameAndPassword);
354 } else if (!username_input.empty()) {
355 RecordAuthenticationMethod(AuthMethod::kUsernameOnly);
356 } else {
357 RecordAuthenticationMethod(AuthMethod::kNoCredentials);
358 }
359
360 // Use provided credentials and parse the username into username and
361 // workgroup if necessary.
362 username = username_input;
363 password = password_input;
364 ParseUserName(username_input, &username, &workgroup);
365 }
366
367 // Construct the file system ID before calling mount so that numerous
368 // arguments don't have to be plumbed through.
369 file_system_provider::MountOptions provider_options(options);
370 if (use_kerberos) {
371 provider_options.file_system_id =
372 CreateFileSystemId(share_path, use_kerberos);
373 } else {
374 std::string full_username;
375 if (save_credentials) {
376 // Only save the username if the user request credentials be saved.
377 full_username = username;
378 if (!workgroup.empty()) {
379 DCHECK(!username.empty());
380 full_username.append("@");
381 full_username.append(workgroup);
382 }
383 }
384 provider_options.file_system_id =
385 CreateFileSystemIdForUser(share_path, full_username);
386 }
387 std::vector<uint8_t> salt;
388 if (save_credentials && !password.empty()) {
389 // Only generate a salt if threre's a password and we've been asked to save
390 // credentials. If there is no password, there's nothing for smbfs to store
391 // and the salt is unused.
392 salt.resize(kSaltLength);
393 crypto::RandBytes(salt);
394 }
395 SmbShareInfo info(parsed_url, options.display_name, username, workgroup,
396 use_kerberos, salt);
397 MountInternal(provider_options, info, password, save_credentials,
398 false /* skip_connect */,
399 base::BindOnce(&SmbService::MountInternalDone,
400 base::Unretained(this), std::move(callback),
401 info, should_open_file_manager_after_mount));
402
403 profile_->GetPrefs()->SetString(prefs::kMostRecentlyUsedNetworkFileShareURL,
404 share_path.value());
405 }
406
MountInternalDone(MountResponse callback,const SmbShareInfo & info,bool should_open_file_manager_after_mount,SmbMountResult result,const base::FilePath & mount_path)407 void SmbService::MountInternalDone(MountResponse callback,
408 const SmbShareInfo& info,
409 bool should_open_file_manager_after_mount,
410 SmbMountResult result,
411 const base::FilePath& mount_path) {
412 if (result != SmbMountResult::kSuccess) {
413 std::move(callback).Run(result);
414 return;
415 }
416
417 DCHECK(!mount_path.empty());
418 if (should_open_file_manager_after_mount) {
419 platform_util::ShowItemInFolder(profile_, mount_path);
420 }
421
422 if (IsSmbFsEnabled()) {
423 registry_.Save(info);
424 }
425
426 RecordMountCount();
427 std::move(callback).Run(SmbMountResult::kSuccess);
428 }
429
MountInternal(const file_system_provider::MountOptions & options,const SmbShareInfo & info,const std::string & password,bool save_credentials,bool skip_connect,MountInternalCallback callback)430 void SmbService::MountInternal(
431 const file_system_provider::MountOptions& options,
432 const SmbShareInfo& info,
433 const std::string& password,
434 bool save_credentials,
435 bool skip_connect,
436 MountInternalCallback callback) {
437 user_manager::User* user =
438 chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
439 DCHECK(user);
440
441 if (IsSmbFsEnabled()) {
442 SmbFsShare::MountOptions smbfs_options;
443 smbfs_options.resolved_host =
444 share_finder_->GetResolvedHost(info.share_url().GetHost());
445 smbfs_options.username = info.username();
446 smbfs_options.workgroup = info.workgroup();
447 smbfs_options.password = password;
448 smbfs_options.allow_ntlm = IsNTLMAuthenticationEnabled();
449 smbfs_options.skip_connect = skip_connect;
450 if (save_credentials && !info.password_salt().empty()) {
451 smbfs_options.save_restore_password = true;
452 smbfs_options.account_hash = user->username_hash();
453 smbfs_options.password_salt = info.password_salt();
454 }
455 if (info.use_kerberos()) {
456 if (user->IsActiveDirectoryUser()) {
457 smbfs_options.kerberos_options =
458 base::make_optional<SmbFsShare::KerberosOptions>(
459 SmbFsShare::KerberosOptions::Source::kActiveDirectory,
460 user->GetAccountId().GetObjGuid());
461 } else if (smb_credentials_updater_) {
462 smbfs_options.kerberos_options =
463 base::make_optional<SmbFsShare::KerberosOptions>(
464 SmbFsShare::KerberosOptions::Source::kKerberos,
465 smb_credentials_updater_->active_account_name());
466 } else {
467 LOG(WARNING) << "No Kerberos credential source available";
468 std::move(callback).Run(SmbMountResult::kAuthenticationFailed, {});
469 return;
470 }
471 }
472
473 std::unique_ptr<SmbFsShare> mount = std::make_unique<SmbFsShare>(
474 profile_, info.share_url(), info.display_name(), smbfs_options);
475 if (smbfs_mounter_creation_callback_) {
476 mount->SetMounterCreationCallbackForTest(
477 smbfs_mounter_creation_callback_);
478 }
479
480 SmbFsShare* raw_mount = mount.get();
481 const std::string mount_id = mount->mount_id();
482 smbfs_shares_[mount_id] = std::move(mount);
483 raw_mount->Mount(base::BindOnce(&SmbService::OnSmbfsMountDone, AsWeakPtr(),
484 mount_id, std::move(callback)));
485 } else {
486 // If using kerberos, the hostname should not be resolved since kerberos
487 // service tickets are keyed on hosname.
488 const SmbUrl url = info.use_kerberos()
489 ? info.share_url()
490 : share_finder_->GetResolvedUrl(info.share_url());
491
492 SmbProviderClient::MountOptions smb_mount_options;
493 smb_mount_options.original_path = info.share_url().ToString();
494 smb_mount_options.username = info.username();
495 smb_mount_options.workgroup = info.workgroup();
496 smb_mount_options.ntlm_enabled = IsNTLMAuthenticationEnabled();
497 smb_mount_options.save_password = save_credentials && !info.use_kerberos();
498 smb_mount_options.account_hash = user->username_hash();
499 smb_mount_options.skip_connect = skip_connect;
500 GetSmbProviderClient()->Mount(
501 base::FilePath(url.ToString()), smb_mount_options,
502 MakeFdWithContents(password),
503 base::BindOnce(&SmbService::OnProviderMountDone, AsWeakPtr(),
504 std::move(callback), options, save_credentials));
505 }
506 }
507
OnSmbfsMountDone(const std::string & smbfs_mount_id,MountInternalCallback callback,SmbMountResult result)508 void SmbService::OnSmbfsMountDone(const std::string& smbfs_mount_id,
509 MountInternalCallback callback,
510 SmbMountResult result) {
511 RecordMountResult(result);
512
513 if (result != SmbMountResult::kSuccess) {
514 smbfs_shares_.erase(smbfs_mount_id);
515 std::move(callback).Run(result, {});
516 return;
517 }
518
519 SmbFsShare* mount = smbfs_shares_[smbfs_mount_id].get();
520 if (!mount) {
521 LOG(ERROR) << "smbfs mount " << smbfs_mount_id << " does not exist";
522 std::move(callback).Run(SmbMountResult::kUnknownFailure, {});
523 return;
524 }
525
526 std::move(callback).Run(SmbMountResult::kSuccess, mount->mount_path());
527 }
528
OnProviderMountDone(MountInternalCallback callback,const file_system_provider::MountOptions & options,bool save_credentials,smbprovider::ErrorType error,int32_t mount_id)529 void SmbService::OnProviderMountDone(
530 MountInternalCallback callback,
531 const file_system_provider::MountOptions& options,
532 bool save_credentials,
533 smbprovider::ErrorType error,
534 int32_t mount_id) {
535 SmbMountResult mount_result = TranslateErrorToMountResult(error);
536 RecordMountResult(mount_result);
537
538 if (mount_result != SmbMountResult::kSuccess) {
539 std::move(callback).Run(mount_result, {});
540 return;
541 }
542
543 DCHECK_GE(mount_id, 0);
544 mount_id_map_[options.file_system_id] = mount_id;
545
546 base::File::Error result =
547 GetProviderService()->MountFileSystem(provider_id_, options);
548 if (result != base::File::FILE_OK) {
549 mount_id_map_.erase(options.file_system_id);
550 // If the password was asked to be saved, remove it.
551 GetSmbProviderClient()->Unmount(
552 mount_id, save_credentials /* remove_password */, base::DoNothing());
553
554 std::move(callback).Run(TranslateErrorToMountResult(result), {});
555 return;
556 }
557
558 base::FilePath mount_path = file_system_provider::util::GetMountPath(
559 profile_, provider_id_, options.file_system_id);
560 std::move(callback).Run(SmbMountResult::kSuccess, mount_path);
561 }
562
GetMountId(const ProvidedFileSystemInfo & info) const563 int32_t SmbService::GetMountId(const ProvidedFileSystemInfo& info) const {
564 const auto iter = mount_id_map_.find(info.file_system_id());
565 if (iter == mount_id_map_.end()) {
566 // Either the mount process has not yet completed, or it failed to provide
567 // us with a mount id.
568 return kInvalidMountId;
569 }
570 return iter->second;
571 }
572
Unmount(const std::string & file_system_id,file_system_provider::Service::UnmountReason reason)573 base::File::Error SmbService::Unmount(
574 const std::string& file_system_id,
575 file_system_provider::Service::UnmountReason reason) {
576 base::File::Error result = GetProviderService()->UnmountFileSystem(
577 provider_id_, file_system_id, reason);
578 // Always erase the mount_id, because at this point, the share has already
579 // been unmounted in smbprovider.
580 mount_id_map_.erase(file_system_id);
581 return result;
582 }
583
GetProviderService() const584 file_system_provider::Service* SmbService::GetProviderService() const {
585 return file_system_provider::Service::Get(profile_);
586 }
587
GetSmbProviderClient() const588 SmbProviderClient* SmbService::GetSmbProviderClient() const {
589 // If the DBusThreadManager or the SmbProviderClient aren't available,
590 // there isn't much we can do. This should only happen when running tests.
591 if (!chromeos::DBusThreadManager::IsInitialized() ||
592 !chromeos::DBusThreadManager::Get()) {
593 return nullptr;
594 }
595 return chromeos::DBusThreadManager::Get()->GetSmbProviderClient();
596 }
597
RestoreMounts()598 void SmbService::RestoreMounts() {
599 std::vector<ProvidedFileSystemInfo> provided_file_systems =
600 GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
601
602 std::vector<SmbUrl> preconfigured_shares =
603 GetPreconfiguredSharePathsForPremount();
604
605 std::vector<SmbShareInfo> saved_smbfs_shares;
606 if (IsSmbFsEnabled()) {
607 // Restore smbfs shares.
608 // TODO(crbug.com/1055571): Migrate saved smbprovider shares to smbfs.
609 saved_smbfs_shares = registry_.GetAll();
610 }
611
612 if (!provided_file_systems.empty() || !saved_smbfs_shares.empty() ||
613 !preconfigured_shares.empty()) {
614 share_finder_->DiscoverHostsInNetwork(base::BindOnce(
615 &SmbService::OnHostsDiscovered, AsWeakPtr(),
616 std::move(provided_file_systems), std::move(saved_smbfs_shares),
617 std::move(preconfigured_shares)));
618 }
619 }
620
OnHostsDiscovered(const std::vector<ProvidedFileSystemInfo> & file_systems,const std::vector<SmbShareInfo> & saved_smbfs_shares,const std::vector<SmbUrl> & preconfigured_shares)621 void SmbService::OnHostsDiscovered(
622 const std::vector<ProvidedFileSystemInfo>& file_systems,
623 const std::vector<SmbShareInfo>& saved_smbfs_shares,
624 const std::vector<SmbUrl>& preconfigured_shares) {
625 for (const auto& file_system : file_systems) {
626 Remount(file_system);
627 }
628 for (const auto& smbfs_share : saved_smbfs_shares) {
629 MountSavedSmbfsShare(smbfs_share);
630 }
631 for (const auto& url : preconfigured_shares) {
632 MountPreconfiguredShare(url);
633 }
634 }
635
OnHostsDiscoveredForUpdateSharePath(int32_t mount_id,const std::string & share_path,StartReadDirIfSuccessfulCallback reply)636 void SmbService::OnHostsDiscoveredForUpdateSharePath(
637 int32_t mount_id,
638 const std::string& share_path,
639 StartReadDirIfSuccessfulCallback reply) {
640 SmbUrl resolved_url(share_path);
641 if (share_finder_->TryResolveUrl(SmbUrl(share_path), &resolved_url)) {
642 UpdateSharePath(mount_id, resolved_url.ToString(), std::move(reply));
643 } else {
644 std::move(reply).Run(false /* should_retry_start_read_dir */);
645 }
646 }
647
Remount(const ProvidedFileSystemInfo & file_system_info)648 void SmbService::Remount(const ProvidedFileSystemInfo& file_system_info) {
649 const base::FilePath share_path =
650 GetSharePathFromFileSystemId(file_system_info.file_system_id());
651 const bool is_kerberos_chromad =
652 IsKerberosChromadFileSystemId(file_system_info.file_system_id());
653
654 std::string workgroup;
655 std::string username;
656
657 user_manager::User* user =
658 chromeos::ProfileHelper::Get()->GetUserByProfile(profile_);
659 DCHECK(user);
660 if (is_kerberos_chromad) {
661 DCHECK(user->IsActiveDirectoryUser());
662
663 ParseUserPrincipalName(user->GetDisplayEmail(), &username, &workgroup);
664 } else {
665 base::Optional<std::string> user_workgroup =
666 GetUserFromFileSystemId(file_system_info.file_system_id());
667 if (user_workgroup &&
668 !ParseUserName(*user_workgroup, &username, &workgroup)) {
669 LOG(ERROR) << "Failed to parse username/workgroup from file system ID";
670 }
671 }
672
673 SmbUrl parsed_url(share_path.value());
674 if (!parsed_url.IsValid()) {
675 OnRemountResponse(file_system_info.file_system_id(),
676 smbprovider::ERROR_INVALID_URL, kInvalidMountId);
677 return;
678 }
679
680 // If using kerberos, the hostname should not be resolved since kerberos
681 // service tickets are keyed on hosname.
682 const SmbUrl resolved_url = is_kerberos_chromad
683 ? parsed_url
684 : share_finder_->GetResolvedUrl(parsed_url);
685
686 // An empty password is passed to Mount to conform with the credentials API
687 // which expects username & workgroup strings along with a password file
688 // descriptor.
689 SmbProviderClient::MountOptions smb_mount_options;
690 smb_mount_options.original_path = parsed_url.ToString();
691 smb_mount_options.username = username;
692 smb_mount_options.workgroup = workgroup;
693 smb_mount_options.ntlm_enabled = IsNTLMAuthenticationEnabled();
694 smb_mount_options.skip_connect = true;
695 smb_mount_options.restore_password =
696 !username.empty() && !is_kerberos_chromad;
697 smb_mount_options.account_hash = user->username_hash();
698 GetSmbProviderClient()->Mount(
699 base::FilePath(resolved_url.ToString()), smb_mount_options,
700 MakeFdWithContents(""),
701 base::BindOnce(&SmbService::OnRemountResponse, AsWeakPtr(),
702 file_system_info.file_system_id()));
703 }
704
OnRemountResponse(const std::string & file_system_id,smbprovider::ErrorType error,int32_t mount_id)705 void SmbService::OnRemountResponse(const std::string& file_system_id,
706 smbprovider::ErrorType error,
707 int32_t mount_id) {
708 RecordRemountResult(TranslateErrorToMountResult(error));
709
710 if (error != smbprovider::ERROR_OK) {
711 LOG(ERROR) << "SmbService: failed to restore filesystem with error: "
712 << error;
713 // Note: The filesystem isn't removed on failure because doing so will
714 // stop persisting the mount. The mount should only be removed as a result
715 // of user action, and not due to failures, which might be transient (i.e.
716 // smbprovider crashed).
717 return;
718 }
719
720 DCHECK_GE(mount_id, 0);
721 mount_id_map_[file_system_id] = mount_id;
722 }
723
MountSavedSmbfsShare(const SmbShareInfo & info)724 void SmbService::MountSavedSmbfsShare(const SmbShareInfo& info) {
725 MountInternal(
726 {} /* fsp::MountOptions, ignored by smbfs */, info, "" /* password */,
727 true /* save_credentials */, true /* skip_connect */,
728 base::BindOnce(
729 [](SmbMountResult result, const base::FilePath& mount_path) {
730 LOG_IF(ERROR, result != SmbMountResult::kSuccess)
731 << "Error restoring saved share: " << static_cast<int>(result);
732 }));
733 }
734
MountPreconfiguredShare(const SmbUrl & share_url)735 void SmbService::MountPreconfiguredShare(const SmbUrl& share_url) {
736 file_system_provider::MountOptions mount_options;
737 mount_options.display_name =
738 base::FilePath(share_url.ToString()).BaseName().value();
739 mount_options.writable = true;
740 // |is_chromad_kerberos| is false because we do not pass user and workgroup
741 // at mount time. Premounts also do not get remounted and currently
742 // |is_chromad_kerberos| is only used at remounts to determine if the share
743 // was mounted with chromad kerberos.
744 // TODO(crbug.com/922269): Support kerberos for preconfigured shares.
745 mount_options.file_system_id = CreateFileSystemId(
746 base::FilePath(share_url.ToString()), false /* is_chromad_kerberos */);
747 // Disable remounting of preconfigured shares.
748 mount_options.persistent = false;
749
750 // Note: Preconfigured shares are mounted without credentials.
751 SmbShareInfo info(share_url, mount_options.display_name, "" /* username */,
752 "" /* workgroup */, false /* use_kerberos */);
753 MountInternal(
754 mount_options, info, "" /* password */, false /* save_credentials */,
755 true /* skip_connect */,
756 base::BindOnce(&SmbService::OnMountPreconfiguredShareDone, AsWeakPtr()));
757 }
758
OnMountPreconfiguredShareDone(SmbMountResult result,const base::FilePath & mount_path)759 void SmbService::OnMountPreconfiguredShareDone(
760 SmbMountResult result,
761 const base::FilePath& mount_path) {
762 LOG_IF(ERROR, result != SmbMountResult::kSuccess)
763 << "Error mounting preconfigured share: " << static_cast<int>(result);
764 }
765
IsKerberosEnabledViaPolicy() const766 bool SmbService::IsKerberosEnabledViaPolicy() const {
767 return smb_credentials_updater_ &&
768 smb_credentials_updater_->IsKerberosEnabled();
769 }
770
SetupKerberos(const std::string & account_identifier)771 void SmbService::SetupKerberos(const std::string& account_identifier) {
772 SmbProviderClient* client = GetSmbProviderClient();
773 if (!client) {
774 return;
775 }
776
777 client->SetupKerberos(
778 account_identifier,
779 base::BindOnce(&SmbService::OnSetupKerberosResponse, AsWeakPtr()));
780 }
781
UpdateKerberosCredentials(const std::string & account_identifier)782 void SmbService::UpdateKerberosCredentials(
783 const std::string& account_identifier) {
784 SmbProviderClient* client = GetSmbProviderClient();
785 if (!client) {
786 return;
787 }
788
789 client->SetupKerberos(
790 account_identifier,
791 base::BindOnce(&SmbService::OnUpdateKerberosCredentialsResponse,
792 AsWeakPtr()));
793 }
794
OnUpdateKerberosCredentialsResponse(bool success)795 void SmbService::OnUpdateKerberosCredentialsResponse(bool success) {
796 LOG_IF(ERROR, !success) << "Update Kerberos credentials failed.";
797 }
798
OnSetupKerberosResponse(bool success)799 void SmbService::OnSetupKerberosResponse(bool success) {
800 if (!success) {
801 LOG(ERROR) << "SmbService: Kerberos setup failed.";
802 }
803
804 CompleteSetup();
805 }
806
CompleteSetup()807 void SmbService::CompleteSetup() {
808 share_finder_ = std::make_unique<SmbShareFinder>(GetSmbProviderClient());
809 RegisterHostLocators();
810
811 GetProviderService()->RegisterProvider(std::make_unique<SmbProvider>(
812 base::BindRepeating(&SmbService::GetMountId, base::Unretained(this)),
813 base::BindRepeating(&SmbService::Unmount, base::Unretained(this)),
814 base::BindRepeating(&SmbService::RequestCredentials,
815 base::Unretained(this)),
816 base::BindRepeating(&SmbService::RequestUpdatedSharePath,
817 base::Unretained(this))));
818 RestoreMounts();
819 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
820 if (chromeos::PowerManagerClient::Get()) {
821 chromeos::PowerManagerClient::Get()->AddObserver(this);
822 }
823
824 if (setup_complete_callback_) {
825 std::move(setup_complete_callback_).Run();
826 }
827 }
828
OnSetupCompleteForTesting(base::OnceClosure callback)829 void SmbService::OnSetupCompleteForTesting(base::OnceClosure callback) {
830 DCHECK(!setup_complete_callback_);
831 if (share_finder_) {
832 std::move(callback).Run();
833 return;
834 }
835 setup_complete_callback_ = std::move(callback);
836 }
837
SetSmbFsMounterCreationCallbackForTesting(SmbFsShare::MounterCreationCallback callback)838 void SmbService::SetSmbFsMounterCreationCallbackForTesting(
839 SmbFsShare::MounterCreationCallback callback) {
840 smbfs_mounter_creation_callback_ = std::move(callback);
841 }
842
RegisterHostLocators()843 void SmbService::RegisterHostLocators() {
844 if (disable_share_discovery_for_testing_) {
845 return;
846 }
847
848 SetUpMdnsHostLocator();
849 if (IsNetBiosDiscoveryEnabled()) {
850 SetUpNetBiosHostLocator();
851 } else {
852 LOG(WARNING) << "SmbService: NetBios discovery disabled.";
853 }
854 }
855
SetUpMdnsHostLocator()856 void SmbService::SetUpMdnsHostLocator() {
857 share_finder_->RegisterHostLocator(std::make_unique<MDnsHostLocator>());
858 }
859
SetUpNetBiosHostLocator()860 void SmbService::SetUpNetBiosHostLocator() {
861 auto get_interfaces = base::BindRepeating(&GetInterfaces);
862 auto client_factory = base::BindRepeating(&GetNetBiosClient, profile_);
863
864 auto netbios_host_locator = std::make_unique<NetBiosHostLocator>(
865 std::move(get_interfaces), std::move(client_factory),
866 GetSmbProviderClient());
867
868 share_finder_->RegisterHostLocator(std::move(netbios_host_locator));
869 }
870
IsNetBiosDiscoveryEnabled() const871 bool SmbService::IsNetBiosDiscoveryEnabled() const {
872 return profile_->GetPrefs()->GetBoolean(prefs::kNetBiosShareDiscoveryEnabled);
873 }
874
IsNTLMAuthenticationEnabled() const875 bool SmbService::IsNTLMAuthenticationEnabled() const {
876 return profile_->GetPrefs()->GetBoolean(
877 prefs::kNTLMShareAuthenticationEnabled);
878 }
879
IsShareMounted(const SmbUrl & share) const880 bool SmbService::IsShareMounted(const SmbUrl& share) const {
881 std::vector<ProvidedFileSystemInfo> file_systems =
882 GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
883
884 for (const auto& info : file_systems) {
885 base::FilePath share_path =
886 GetSharePathFromFileSystemId(info.file_system_id());
887 SmbUrl parsed_url(share_path.value());
888 DCHECK(parsed_url.IsValid());
889 if (parsed_url.ToString() == share.ToString()) {
890 return true;
891 }
892 }
893
894 for (const auto& entry : smbfs_shares_) {
895 if (entry.second->share_url().ToString() == share.ToString()) {
896 return true;
897 }
898 }
899 return false;
900 }
901
GetPreconfiguredSharePaths(const std::string & policy_mode) const902 std::vector<SmbUrl> SmbService::GetPreconfiguredSharePaths(
903 const std::string& policy_mode) const {
904 std::vector<SmbUrl> preconfigured_urls;
905
906 const base::Value* preconfigured_shares = profile_->GetPrefs()->GetList(
907 prefs::kNetworkFileSharesPreconfiguredShares);
908
909 for (const base::Value& info : preconfigured_shares->GetList()) {
910 // |info| is a dictionary with entries for |share_url| and |mode|.
911 const base::Value* share_url = info.FindKey(kShareUrlKey);
912 const base::Value* mode = info.FindKey(kModeKey);
913
914 if (policy_mode == kModeUnknownValue) {
915 // kModeUnknownValue is used to filter for any shares that do not match
916 // a presently known mode for preconfiguration. As new preconfigure
917 // modes are added, this should be kept in sync.
918 if (mode->GetString() != kModeDropDownValue &&
919 mode->GetString() != kModePreMountValue) {
920 preconfigured_urls.emplace_back(share_url->GetString());
921 }
922
923 } else {
924 // Filter normally
925 if (mode->GetString() == policy_mode) {
926 preconfigured_urls.emplace_back(share_url->GetString());
927 }
928 }
929 }
930 return preconfigured_urls;
931 }
932
RequestCredentials(const std::string & share_path,int32_t mount_id,base::OnceClosure reply)933 void SmbService::RequestCredentials(const std::string& share_path,
934 int32_t mount_id,
935 base::OnceClosure reply) {
936 smb_dialog::SmbCredentialsDialog::Show(
937 base::NumberToString(mount_id), share_path,
938 base::BindOnce(&SmbService::OnSmbCredentialsDialogShown, AsWeakPtr(),
939 mount_id, std::move(reply)));
940 }
941
OnSmbCredentialsDialogShown(int32_t mount_id,base::OnceClosure reply,bool canceled,const std::string & username,const std::string & password)942 void SmbService::OnSmbCredentialsDialogShown(int32_t mount_id,
943 base::OnceClosure reply,
944 bool canceled,
945 const std::string& username,
946 const std::string& password) {
947 if (canceled) {
948 return;
949 }
950
951 std::string parsed_username = username;
952 std::string workgroup;
953 ParseUserName(username, &parsed_username, &workgroup);
954
955 GetSmbProviderClient()->UpdateMountCredentials(
956 mount_id, workgroup, parsed_username, MakeFdWithContents(password),
957 base::BindOnce(
958 [](int32_t mount_id, base::OnceClosure reply,
959 smbprovider::ErrorType error) {
960 if (error == smbprovider::ERROR_OK) {
961 std::move(reply).Run();
962 } else {
963 LOG(ERROR) << "Failed to update the credentials for mount id "
964 << mount_id;
965 }
966 },
967 mount_id, std::move(reply)));
968 }
969
GetPreconfiguredSharePathsForDropdown() const970 std::vector<SmbUrl> SmbService::GetPreconfiguredSharePathsForDropdown() const {
971 auto drop_down_paths = GetPreconfiguredSharePaths(kModeDropDownValue);
972 auto fallback_paths = GetPreconfiguredSharePaths(kModeUnknownValue);
973
974 for (auto&& fallback_path : fallback_paths) {
975 drop_down_paths.push_back(std::move(fallback_path));
976 }
977
978 return drop_down_paths;
979 }
980
GetPreconfiguredSharePathsForPremount() const981 std::vector<SmbUrl> SmbService::GetPreconfiguredSharePathsForPremount() const {
982 return GetPreconfiguredSharePaths(kModePreMountValue);
983 }
984
RequestUpdatedSharePath(const std::string & share_path,int32_t mount_id,StartReadDirIfSuccessfulCallback reply)985 void SmbService::RequestUpdatedSharePath(
986 const std::string& share_path,
987 int32_t mount_id,
988 StartReadDirIfSuccessfulCallback reply) {
989 if (ShouldRunHostDiscoveryAgain()) {
990 previous_host_discovery_time_ = tick_clock_->NowTicks();
991 share_finder_->DiscoverHostsInNetwork(
992 base::BindOnce(&SmbService::OnHostsDiscoveredForUpdateSharePath,
993 AsWeakPtr(), mount_id, share_path, std::move(reply)));
994 return;
995 }
996 // Host discovery did not run, but try to resolve the hostname in case a
997 // previous host discovery found the host.
998 SmbUrl resolved_url(share_path);
999 if (share_finder_->TryResolveUrl(SmbUrl(share_path), &resolved_url)) {
1000 UpdateSharePath(mount_id, share_path, std::move(reply));
1001 } else {
1002 std::move(reply).Run(false /* should_retry_start_read_dir */);
1003 }
1004 }
1005
ShouldRunHostDiscoveryAgain() const1006 bool SmbService::ShouldRunHostDiscoveryAgain() const {
1007 return tick_clock_->NowTicks() >
1008 previous_host_discovery_time_ + kHostDiscoveryInterval;
1009 }
1010
OnNetworkChanged(net::NetworkChangeNotifier::ConnectionType type)1011 void SmbService::OnNetworkChanged(
1012 net::NetworkChangeNotifier::ConnectionType type) {
1013 // Run host discovery to refresh list of cached hosts for subsequent name
1014 // resolution attempts.
1015 share_finder_->DiscoverHostsInNetwork(base::DoNothing()
1016 /* HostDiscoveryResponse */);
1017 }
1018
RecordMountCount() const1019 void SmbService::RecordMountCount() const {
1020 const std::vector<ProvidedFileSystemInfo> file_systems =
1021 GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
1022 UMA_HISTOGRAM_COUNTS_100("NativeSmbFileShare.MountCount",
1023 file_systems.size() + smbfs_shares_.size());
1024 }
1025
SuspendImminent(power_manager::SuspendImminent::Reason reason)1026 void SmbService::SuspendImminent(
1027 power_manager::SuspendImminent::Reason reason) {
1028 for (auto it = smbfs_shares_.begin(); it != smbfs_shares_.end(); ++it) {
1029 SmbFsShare* share = it->second.get();
1030
1031 // For each share, block suspend until the unmount has completed, to ensure
1032 // that no smbfs instances are active when the system goes to sleep.
1033 auto token = base::UnguessableToken::Create();
1034 chromeos::PowerManagerClient::Get()->BlockSuspend(token, "SmbService");
1035 share->Unmount(
1036 base::BindOnce(&SmbService::OnSuspendUnmountDone, AsWeakPtr(), token));
1037 }
1038 }
1039
OnSuspendUnmountDone(base::UnguessableToken power_manager_suspend_token,chromeos::MountError result)1040 void SmbService::OnSuspendUnmountDone(
1041 base::UnguessableToken power_manager_suspend_token,
1042 chromeos::MountError result) {
1043 LOG_IF(ERROR, result != chromeos::MountError::MOUNT_ERROR_NONE)
1044 << "Could not unmount smbfs share during suspension: "
1045 << static_cast<int>(result);
1046 // Regardless of the outcome, unblock suspension for this share.
1047 chromeos::PowerManagerClient::Get()->UnblockSuspend(
1048 power_manager_suspend_token);
1049 }
1050
SuspendDone(const base::TimeDelta & sleep_duration)1051 void SmbService::SuspendDone(const base::TimeDelta& sleep_duration) {
1052 // Don't iterate directly over the share map during the remount
1053 // process as shares can be removed on failure in OnSmbfsMountDone.
1054 std::vector<std::string> mount_ids;
1055 for (const auto& s : smbfs_shares_)
1056 mount_ids.push_back(s.first);
1057
1058 for (const auto& mount_id : mount_ids) {
1059 auto share_it = smbfs_shares_.find(mount_id);
1060 if (share_it == smbfs_shares_.end()) {
1061 LOG(WARNING) << "Smbfs mount id " << mount_id
1062 << " no longer present during remount after suspend";
1063 continue;
1064 }
1065 SmbFsShare* share = share_it->second.get();
1066
1067 // Don't try to reconnect as we race the network stack in getting an IP
1068 // address.
1069 SmbFsShare::MountOptions options = share->options();
1070 options.skip_connect = true;
1071 // Observing power management changes from SmbService allows us to remove
1072 // the share in OnSmbfsMountDone if remount fails.
1073 share->Remount(
1074 options, base::BindOnce(
1075 &SmbService::OnSmbfsMountDone, AsWeakPtr(), mount_id,
1076 base::BindOnce([](SmbMountResult result,
1077 const base::FilePath& mount_path) {
1078 LOG_IF(ERROR, result != SmbMountResult::kSuccess)
1079 << "Error remounting smbfs share after suspension: "
1080 << static_cast<int>(result);
1081 })));
1082 }
1083 }
1084
1085 } // namespace smb_client
1086 } // namespace chromeos
1087