// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/profile.h" #include #include #include #include #include #include #include #include #if defined(__ANDROID__) #include #else #include #endif // __ANDROID__ #include "shill/adaptor_interfaces.h" #include "shill/control_interface.h" #include "shill/logging.h" #include "shill/manager.h" #include "shill/property_accessor.h" #include "shill/service.h" #include "shill/store_factory.h" #include "shill/store_interface.h" #include "shill/stub_storage.h" using base::FilePath; using std::set; using std::string; using std::vector; namespace shill { #if defined(ENABLE_JSON_STORE) namespace { const char kFileExtensionJson[] = "json"; } #endif // static const char Profile::kUserProfileListPathname[] = RUNDIR "/loaded_profile_list"; Profile::Profile(ControlInterface* control_interface, Metrics* metrics, Manager* manager, const Identifier& name, const base::FilePath& storage_directory, bool connect_to_rpc) : metrics_(metrics), manager_(manager), control_interface_(control_interface), name_(name) { if (connect_to_rpc) adaptor_.reset(control_interface->CreateProfileAdaptor(this)); // kCheckPortalListProperty: Registered in DefaultProfile // kCountryProperty: Registered in DefaultProfile store_.RegisterConstString(kNameProperty, &name_.identifier); store_.RegisterConstString(kUserHashProperty, &name_.user_hash); // kOfflineModeProperty: Registered in DefaultProfile // kPortalURLProperty: Registered in DefaultProfile HelpRegisterConstDerivedStrings(kServicesProperty, &Profile::EnumerateAvailableServices); HelpRegisterConstDerivedStrings(kEntriesProperty, &Profile::EnumerateEntries); if (name.user.empty()) { // Subtle: Profile is only directly instantiated for user // profiles. And user profiles must have a non-empty // |name.user|. So we want to CHECK here. But Profile is also the // base class for DefaultProfile. So a CHECK here would cause us // to abort whenever we attempt to instantiate a DefaultProfile. // // Instead, we leave |persistent_profile_path_| unintialized. One // of two things will happen: a) we become a DefaultProfile, and // the DefaultProfile ctor sets |persistent_profile_path_|, or b) // we really are destined to be a user Profile. In the latter // case, our |name| argument was invalid, // |persistent_profile_path_| is never set, and we CHECK for an // empty |persistent_profile_path_| in InitStorage(). // // TODO(quiche): Clean this up. crbug.com/527553 } else { persistent_profile_path_ = GetFinalStoragePath(storage_directory, name); } } Profile::~Profile() {} bool Profile::InitStorage(InitStorageOption storage_option, Error* error) { CHECK(!persistent_profile_path_.empty()); std::unique_ptr storage( StoreFactory::GetInstance()->CreateStore(persistent_profile_path_)); bool already_exists = storage->IsNonEmpty(); if (!already_exists && storage_option != kCreateNew && storage_option != kCreateOrOpenExisting) { Error::PopulateAndLog( FROM_HERE, error, Error::kNotFound, base::StringPrintf("Profile storage for %s:%s does not already exist", name_.user.c_str(), name_.identifier.c_str())); return false; } else if (already_exists && storage_option != kOpenExisting && storage_option != kCreateOrOpenExisting) { Error::PopulateAndLog( FROM_HERE, error, Error::kAlreadyExists, base::StringPrintf("Profile storage for %s:%s already exists", name_.user.c_str(), name_.identifier.c_str())); return false; } if (!storage->Open()) { Error::PopulateAndLog( FROM_HERE, error, Error::kInternalError, base::StringPrintf("Could not open profile storage for %s:%s", name_.user.c_str(), name_.identifier.c_str())); if (already_exists) { // The profile contents are corrupt, or we do not have access to // this file. Move this file out of the way so a future open attempt // will succeed, assuming the failure reason was the former. storage->MarkAsCorrupted(); metrics_->NotifyCorruptedProfile(); } return false; } if (!already_exists) { // Add a descriptive header to the profile so even if nothing is stored // to it, it still has some content. Completely empty keyfiles are not // valid for reading. storage->SetHeader( base::StringPrintf("Profile %s:%s", name_.user.c_str(), name_.identifier.c_str())); } set_storage(storage.release()); manager_->OnProfileStorageInitialized(this); return true; } void Profile::InitStubStorage() { set_storage(new StubStorage()); } bool Profile::RemoveStorage(Error* error) { CHECK(!storage_.get()); CHECK(!persistent_profile_path_.empty()); if (!base::DeleteFile(persistent_profile_path_, false)) { Error::PopulateAndLog( FROM_HERE, error, Error::kOperationFailed, base::StringPrintf("Could not remove path %s", persistent_profile_path_.value().c_str())); return false; } return true; } string Profile::GetFriendlyName() { return (name_.user.empty() ? "" : name_.user + "/") + name_.identifier; } string Profile::GetRpcIdentifier() { if (!adaptor_.get()) { return string(); } return adaptor_->GetRpcIdentifier(); } void Profile::set_storage(StoreInterface* storage) { storage_.reset(storage); } bool Profile::AdoptService(const ServiceRefPtr& service) { if (service->profile() == this) { return false; } service->SetProfile(this); return service->Save(storage_.get()) && storage_->Flush(); } bool Profile::AbandonService(const ServiceRefPtr& service) { if (service->profile() == this) service->SetProfile(nullptr); return storage_->DeleteGroup(service->GetStorageIdentifier()) && storage_->Flush(); } bool Profile::UpdateService(const ServiceRefPtr& service) { return service->Save(storage_.get()) && storage_->Flush(); } bool Profile::LoadService(const ServiceRefPtr& service) { if (!ContainsService(service)) return false; return service->Load(storage_.get()); } bool Profile::ConfigureService(const ServiceRefPtr& service) { if (!LoadService(service)) return false; service->SetProfile(this); return true; } bool Profile::ConfigureDevice(const DeviceRefPtr& device) { return device->Load(storage_.get()); } bool Profile::ContainsService(const ServiceConstRefPtr& service) { return service->IsLoadableFrom(*storage_.get()); } void Profile::DeleteEntry(const std::string& entry_name, Error* error) { if (!storage_->ContainsGroup(entry_name)) { Error::PopulateAndLog( FROM_HERE, error, Error::kNotFound, base::StringPrintf("Entry %s does not exist in profile", entry_name.c_str())); return; } if (!manager_->HandleProfileEntryDeletion(this, entry_name)) { // If HandleProfileEntryDeletion() returns succeeds, DeleteGroup() // has already been called when AbandonService was called. // Otherwise, we need to delete the group ourselves. storage_->DeleteGroup(entry_name); } Save(); } ServiceRefPtr Profile::GetServiceFromEntry(const std::string& entry_name, Error* error) { if (!storage_->ContainsGroup(entry_name)) { Error::PopulateAndLog( FROM_HERE, error, Error::kNotFound, base::StringPrintf("Entry %s does not exist in profile", entry_name.c_str())); return nullptr; } // Lookup the service entry from the registered services. ServiceRefPtr service = manager_->GetServiceWithStorageIdentifier(this, entry_name, error); if (service) { return service; } // Load the service entry to a temporary service. return manager_->CreateTemporaryServiceFromProfile(this, entry_name, error); } bool Profile::IsValidIdentifierToken(const string& token) { if (token.empty()) { return false; } for (auto chr : token) { if (!base::IsAsciiAlpha(chr) && !base::IsAsciiDigit(chr)) { return false; } } return true; } // static bool Profile::ParseIdentifier(const string& raw, Identifier* parsed) { if (raw.empty()) { return false; } if (raw[0] == '~') { // Format: "~user/identifier". size_t slash = raw.find('/'); if (slash == string::npos) { return false; } string user(raw.begin() + 1, raw.begin() + slash); string identifier(raw.begin() + slash + 1, raw.end()); if (!IsValidIdentifierToken(user) || !IsValidIdentifierToken(identifier)) { return false; } parsed->user = user; parsed->identifier = identifier; return true; } // Format: "identifier". if (!IsValidIdentifierToken(raw)) { return false; } parsed->user = ""; parsed->identifier = raw; return true; } // static string Profile::IdentifierToString(const Identifier& name) { if (name.user.empty()) { // Format: "identifier". return name.identifier; } // Format: "~user/identifier". return base::StringPrintf( "~%s/%s", name.user.c_str(), name.identifier.c_str()); } // static vector Profile::LoadUserProfileList(const FilePath& path) { vector profile_identifiers; string profile_data; if (!base::ReadFileToString(path, &profile_data)) { return profile_identifiers; } vector profile_lines = base::SplitString(profile_data, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& line : profile_lines) { if (line.empty()) { // This will be the case on the last line, so let's not complain about it. continue; } size_t space = line.find(' '); if (space == string::npos || space == 0) { LOG(ERROR) << "Invalid line found in " << path.value() << ": " << line; continue; } string name(line.begin(), line.begin() + space); Identifier identifier; if (!ParseIdentifier(name, &identifier) || identifier.user.empty()) { LOG(ERROR) << "Invalid profile name found in " << path.value() << ": " << name; continue; } identifier.user_hash = string(line.begin() + space + 1, line.end()); profile_identifiers.push_back(identifier); } return profile_identifiers; } // static bool Profile::SaveUserProfileList(const FilePath& path, const vector& profiles) { vector lines; for (const auto& profile : profiles) { Identifier& id = profile->name_; if (id.user.empty()) { continue; } lines.push_back(base::StringPrintf("%s %s\n", IdentifierToString(id).c_str(), id.user_hash.c_str())); } string content = base::JoinString(lines, ""); size_t ret = base::WriteFile(path, content.c_str(), content.length()); return ret == content.length(); } bool Profile::MatchesIdentifier(const Identifier& name) const { return name.user == name_.user && name.identifier == name_.identifier; } bool Profile::Save() { return storage_->Flush(); } vector Profile::EnumerateAvailableServices(Error* error) { // We should return the Manager's service list if this is the active profile. if (manager_->IsActiveProfile(this)) { return manager_->EnumerateAvailableServices(error); } else { return vector(); } } vector Profile::EnumerateEntries(Error* /*error*/) { vector service_groups; // Filter this list down to only entries that correspond // to a technology. (wifi_*, etc) for (const auto& group : storage_->GetGroups()) { if (Technology::IdentifierFromStorageGroup(group) != Technology::kUnknown) service_groups.push_back(group); } return service_groups; } bool Profile::UpdateDevice(const DeviceRefPtr& device) { return false; } #if !defined(DISABLE_WIFI) bool Profile::UpdateWiFiProvider(const WiFiProvider& wifi_provider) { return false; } #endif // DISABLE_WIFI void Profile::HelpRegisterConstDerivedStrings( const string& name, Strings(Profile::*get)(Error*)) { store_.RegisterDerivedStrings( name, StringsAccessor( new CustomAccessor(this, get, nullptr))); } // static FilePath Profile::GetFinalStoragePath( const FilePath& storage_dir, const Identifier& profile_name) { FilePath base_path; if (profile_name.user.empty()) { // True for DefaultProfiles. base_path = storage_dir.Append( base::StringPrintf("%s.profile", profile_name.identifier.c_str())); } else { base_path = storage_dir.Append( base::StringPrintf("%s/%s.profile", profile_name.user.c_str(), profile_name.identifier.c_str())); } // TODO(petkov): Validate the directory permissions, etc. #if defined(ENABLE_JSON_STORE) return base_path.AddExtension(kFileExtensionJson); #else return base_path; #endif } } // namespace shill