Home | History | Annotate | Download | only in util
      1 // Copyright (c) 2010 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 // This class isn't pretty. It's just a step better than globals, which is what
      6 // these were previously.
      7 
      8 #include "chrome/browser/sync/util/user_settings.h"
      9 
     10 #include "build/build_config.h"
     11 
     12 #if defined(OS_WIN)
     13 #include <windows.h>
     14 #endif
     15 
     16 #include <limits>
     17 #include <string>
     18 #include <vector>
     19 
     20 #include "base/file_util.h"
     21 #include "base/string_util.h"
     22 #include "chrome/browser/sync/syncable/directory_manager.h"  // For migration.
     23 #include "chrome/browser/sync/util/crypto_helpers.h"
     24 #include "chrome/browser/sync/util/data_encryption.h"
     25 #include "chrome/common/sqlite_utils.h"
     26 
     27 using std::numeric_limits;
     28 using std::string;
     29 using std::vector;
     30 
     31 using syncable::DirectoryManager;
     32 
     33 namespace browser_sync {
     34 
     35 void ExecOrDie(sqlite3* dbhandle, const char *query) {
     36   SQLStatement statement;
     37   statement.prepare(dbhandle, query);
     38   if (SQLITE_DONE != statement.step()) {
     39     LOG(FATAL) << query << "\n" << sqlite3_errmsg(dbhandle);
     40   }
     41 }
     42 
     43 // Useful for encoding any sequence of bytes into a string that can be used in
     44 // a table name. Kind of like hex encoding, except that A is zero and P is 15.
     45 string APEncode(const string& in) {
     46   string result;
     47   result.reserve(in.size() * 2);
     48   for (string::const_iterator i = in.begin(); i != in.end(); ++i) {
     49     unsigned int c = static_cast<unsigned char>(*i);
     50     result.push_back((c & 0x0F) + 'A');
     51     result.push_back(((c >> 4) & 0x0F) + 'A');
     52   }
     53   return result;
     54 }
     55 
     56 string APDecode(const string& in) {
     57   string result;
     58   result.reserve(in.size() / 2);
     59   for (string::const_iterator i = in.begin(); i != in.end(); ++i) {
     60     unsigned int c = *i - 'A';
     61     if (++i != in.end())
     62       c = c | (static_cast<unsigned char>(*i - 'A') << 4);
     63     result.push_back(c);
     64   }
     65   return result;
     66 }
     67 
     68 static const char PASSWORD_HASH[] = "password_hash2";
     69 static const char SALT[] = "salt2";
     70 
     71 static const int kSaltSize = 20;
     72 static const int kCurrentDBVersion = 12;
     73 
     74 UserSettings::ScopedDBHandle::ScopedDBHandle(UserSettings* settings)
     75     : mutex_lock_(settings->dbhandle_mutex_), handle_(&settings->dbhandle_) {
     76 }
     77 
     78 UserSettings::UserSettings() : dbhandle_(NULL) {
     79 }
     80 
     81 string UserSettings::email() const {
     82   base::AutoLock lock(mutex_);
     83   return email_;
     84 }
     85 
     86 static void MakeSigninsTable(sqlite3* const dbhandle) {
     87   // Multiple email addresses can map to the same Google Account. This table
     88   // keeps a map of sign-in email addresses to primary Google Account email
     89   // addresses.
     90   ExecOrDie(dbhandle,
     91             "CREATE TABLE signins"
     92             " (signin, primary_email, "
     93             " PRIMARY KEY(signin, primary_email) ON CONFLICT REPLACE)");
     94 }
     95 
     96 void UserSettings::MigrateOldVersionsAsNeeded(sqlite3* const handle,
     97     int current_version) {
     98   switch (current_version) {
     99     // Versions 1-9 are unhandled.  Version numbers greater than
    100     // kCurrentDBVersion should have already been weeded out by the caller.
    101     default:
    102       // When the version is too old, we just try to continue anyway.  There
    103       // should not be a released product that makes a database too old for us
    104       // to handle.
    105       LOG(WARNING) << "UserSettings database version " << current_version <<
    106           " is too old to handle.";
    107       return;
    108     case 10:
    109       {
    110         // Scrape the 'shares' table to find the syncable DB.  'shares' had a
    111         // pair of string columns that mapped the username to the filename of
    112         // the sync data sqlite3 file.  Version 11 switched to a constant
    113         // filename, so here we read the string, copy the file to the new name,
    114         // delete the old one, and then drop the unused shares table.
    115         SQLStatement share_query;
    116         share_query.prepare(handle, "SELECT share_name, file_name FROM shares");
    117         int query_result = share_query.step();
    118         CHECK(SQLITE_ROW == query_result);
    119         FilePath::StringType share_name, file_name;
    120 #if defined(OS_POSIX)
    121         share_name = share_query.column_string(0);
    122         file_name = share_query.column_string(1);
    123 #else
    124         share_name = share_query.column_wstring(0);
    125         file_name = share_query.column_wstring(1);
    126 #endif
    127 
    128         const FilePath& src_syncdata_path = FilePath(file_name);
    129         FilePath dst_syncdata_path(src_syncdata_path.DirName());
    130         file_util::AbsolutePath(&dst_syncdata_path);
    131         dst_syncdata_path = dst_syncdata_path.Append(
    132             DirectoryManager::GetSyncDataDatabaseFilename());
    133         if (!file_util::Move(src_syncdata_path, dst_syncdata_path)) {
    134           LOG(WARNING) << "Unable to upgrade UserSettings from v10";
    135           return;
    136         }
    137       }
    138       ExecOrDie(handle, "DROP TABLE shares");
    139       ExecOrDie(handle, "UPDATE db_version SET version = 11");
    140     // FALL THROUGH
    141     case 11:
    142       ExecOrDie(handle, "DROP TABLE signin_types");
    143       ExecOrDie(handle, "UPDATE db_version SET version = 12");
    144     // FALL THROUGH
    145     case kCurrentDBVersion:
    146       // Nothing to migrate.
    147       return;
    148   }
    149 }
    150 
    151 static void MakeCookiesTable(sqlite3* const dbhandle) {
    152   // This table keeps a list of auth tokens for each signed in account. There
    153   // will be as many rows as there are auth tokens per sign in.
    154   // The service_token column will store encrypted values.
    155   ExecOrDie(dbhandle,
    156             "CREATE TABLE cookies"
    157             " (email, service_name, service_token, "
    158             " PRIMARY KEY(email, service_name) ON CONFLICT REPLACE)");
    159 }
    160 
    161 static void MakeClientIDTable(sqlite3* const dbhandle) {
    162   // Stores a single client ID value that can be used as the client id, if
    163   // there's not another such ID provided on the install.
    164   ExecOrDie(dbhandle, "CREATE TABLE client_id (id) ");
    165   {
    166     SQLStatement statement;
    167     statement.prepare(dbhandle,
    168                       "INSERT INTO client_id values ( ? )");
    169     statement.bind_string(0, Generate128BitRandomHexString());
    170     if (SQLITE_DONE != statement.step()) {
    171       LOG(FATAL) << "INSERT INTO client_id\n" << sqlite3_errmsg(dbhandle);
    172     }
    173   }
    174 }
    175 
    176 bool UserSettings::Init(const FilePath& settings_path) {
    177   {  // Scope the handle.
    178     ScopedDBHandle dbhandle(this);
    179     if (dbhandle_)
    180       sqlite3_close(dbhandle_);
    181 
    182     if (SQLITE_OK != sqlite_utils::OpenSqliteDb(settings_path, &dbhandle_))
    183       return false;
    184 
    185     // In the worst case scenario, the user may hibernate his computer during
    186     // one of our transactions.
    187     sqlite3_busy_timeout(dbhandle_, numeric_limits<int>::max());
    188     ExecOrDie(dbhandle.get(), "PRAGMA fullfsync = 1");
    189     ExecOrDie(dbhandle.get(), "PRAGMA synchronous = 2");
    190 
    191     SQLTransaction transaction(dbhandle.get());
    192     transaction.BeginExclusive();
    193     SQLStatement table_query;
    194     table_query.prepare(dbhandle.get(),
    195                         "select count(*) from sqlite_master"
    196                         " where type = 'table' and name = 'db_version'");
    197     int query_result = table_query.step();
    198     CHECK(SQLITE_ROW == query_result);
    199     int table_count = table_query.column_int(0);
    200     table_query.reset();
    201     if (table_count > 0) {
    202       SQLStatement version_query;
    203       version_query.prepare(dbhandle.get(),
    204                             "SELECT version FROM db_version");
    205       query_result = version_query.step();
    206       CHECK(SQLITE_ROW == query_result);
    207       const int version = version_query.column_int(0);
    208       version_query.reset();
    209       if (version > kCurrentDBVersion) {
    210         LOG(WARNING) << "UserSettings database is too new.";
    211         return false;
    212       }
    213 
    214       MigrateOldVersionsAsNeeded(dbhandle.get(), version);
    215     } else {
    216       // Create settings table.
    217       {
    218         SQLStatement statement;
    219         statement.prepare(dbhandle.get(),
    220                           "CREATE TABLE settings"
    221                           " (email, key, value, "
    222                           "  PRIMARY KEY(email, key) ON CONFLICT REPLACE)");
    223         if (SQLITE_DONE != statement.step()) {
    224           return false;
    225         }
    226       }
    227       // Create and populate version table.
    228       {
    229         SQLStatement statement;
    230         statement.prepare(dbhandle.get(),
    231                           "CREATE TABLE db_version ( version )");
    232         if (SQLITE_DONE != statement.step()) {
    233           return false;
    234         }
    235       }
    236       {
    237         SQLStatement statement;
    238         statement.prepare(dbhandle.get(),
    239                           "INSERT INTO db_version values ( ? )");
    240         statement.bind_int(0, kCurrentDBVersion);
    241         if (SQLITE_DONE != statement.step()) {
    242           return false;
    243         }
    244       }
    245 
    246       MakeSigninsTable(dbhandle.get());
    247       MakeCookiesTable(dbhandle.get());
    248       MakeClientIDTable(dbhandle.get());
    249     }
    250     transaction.Commit();
    251   }
    252 #if defined(OS_WIN)
    253   // Do not index this file. Scanning can occur every time we close the file,
    254   // which causes long delays in SQLite's file locking.
    255   const DWORD attrs = GetFileAttributes(settings_path.value().c_str());
    256   const BOOL attrs_set =
    257     SetFileAttributes(settings_path.value().c_str(),
    258                       attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
    259 #endif
    260   return true;
    261 }
    262 
    263 UserSettings::~UserSettings() {
    264   if (dbhandle_)
    265     sqlite3_close(dbhandle_);
    266 }
    267 
    268 const int32 kInvalidHash = 0xFFFFFFFF;
    269 
    270 // We use 10 bits of data from the MD5 digest as the hash.
    271 const int32 kHashMask = 0x3FF;
    272 
    273 int32 GetHashFromDigest(const vector<uint8>& digest) {
    274   int32 hash = 0;
    275   int32 mask = kHashMask;
    276   for (vector<uint8>::const_iterator i = digest.begin(); i != digest.end();
    277        ++i) {
    278     hash = hash << 8;
    279     hash = hash | (*i & kHashMask);
    280     mask = mask >> 8;
    281     if (0 == mask)
    282       break;
    283   }
    284   return hash;
    285 }
    286 
    287 void UserSettings::StoreEmailForSignin(const string& signin,
    288                                        const string& primary_email) {
    289   ScopedDBHandle dbhandle(this);
    290   SQLTransaction transaction(dbhandle.get());
    291   int sqlite_result = transaction.BeginExclusive();
    292   CHECK(SQLITE_OK == sqlite_result);
    293   SQLStatement query;
    294   query.prepare(dbhandle.get(),
    295                 "SELECT COUNT(*) FROM signins"
    296                 " WHERE signin = ? AND primary_email = ?");
    297   query.bind_string(0, signin);
    298   query.bind_string(1, primary_email);
    299   int query_result = query.step();
    300   CHECK(SQLITE_ROW == query_result);
    301   int32 count = query.column_int(0);
    302   query.reset();
    303   if (0 == count) {
    304     // Migrate any settings the user might have from earlier versions.
    305     {
    306       SQLStatement statement;
    307       statement.prepare(dbhandle.get(),
    308                         "UPDATE settings SET email = ? WHERE email = ?");
    309       statement.bind_string(0, signin);
    310       statement.bind_string(1, primary_email);
    311       if (SQLITE_DONE != statement.step()) {
    312         LOG(FATAL) << sqlite3_errmsg(dbhandle.get());
    313       }
    314     }
    315     // Store this signin:email mapping.
    316     {
    317       SQLStatement statement;
    318       statement.prepare(dbhandle.get(),
    319                         "INSERT INTO signins(signin, primary_email)"
    320                         " values ( ?, ? )");
    321       statement.bind_string(0, signin);
    322       statement.bind_string(1, primary_email);
    323       if (SQLITE_DONE != statement.step()) {
    324         LOG(FATAL) << sqlite3_errmsg(dbhandle.get());
    325       }
    326     }
    327   }
    328   transaction.Commit();
    329 }
    330 
    331 // string* signin is both the input and the output of this function.
    332 bool UserSettings::GetEmailForSignin(string* signin) {
    333   ScopedDBHandle dbhandle(this);
    334   string result;
    335   SQLStatement query;
    336   query.prepare(dbhandle.get(),
    337                 "SELECT primary_email FROM signins WHERE signin = ?");
    338   query.bind_string(0, *signin);
    339   int query_result = query.step();
    340   if (SQLITE_ROW == query_result) {
    341     query.column_string(0, &result);
    342     if (!result.empty()) {
    343       swap(result, *signin);
    344       return true;
    345     }
    346   }
    347   return false;
    348 }
    349 
    350 void UserSettings::StoreHashedPassword(const string& email,
    351                                        const string& password) {
    352   // Save one-way hashed password:
    353   char binary_salt[kSaltSize];
    354   GetRandomBytes(binary_salt, sizeof(binary_salt));
    355 
    356   const string salt = APEncode(string(binary_salt, sizeof(binary_salt)));
    357   MD5Calculator md5;
    358   md5.AddData(salt.data(), salt.size());
    359   md5.AddData(password.data(), password.size());
    360   ScopedDBHandle dbhandle(this);
    361   SQLTransaction transaction(dbhandle.get());
    362   transaction.BeginExclusive();
    363   {
    364     SQLStatement statement;
    365     statement.prepare(dbhandle.get(),
    366                       "INSERT INTO settings(email, key, value)"
    367                       " values ( ?, ?, ? )");
    368     statement.bind_string(0, email);
    369     statement.bind_string(1, PASSWORD_HASH);
    370     statement.bind_int(2, GetHashFromDigest(md5.GetDigest()));
    371     if (SQLITE_DONE != statement.step()) {
    372       LOG(FATAL) << sqlite3_errmsg(dbhandle.get());
    373     }
    374   }
    375   {
    376     SQLStatement statement;
    377     statement.prepare(dbhandle.get(),
    378                       "INSERT INTO settings(email, key, value)"
    379                       " values ( ?, ?, ? )");
    380     statement.bind_string(0, email);
    381     statement.bind_string(1, SALT);
    382     statement.bind_string(2, salt);
    383     if (SQLITE_DONE != statement.step()) {
    384       LOG(FATAL) << sqlite3_errmsg(dbhandle.get());
    385     }
    386   }
    387   transaction.Commit();
    388 }
    389 
    390 bool UserSettings::VerifyAgainstStoredHash(const string& email,
    391                                            const string& password) {
    392   ScopedDBHandle dbhandle(this);
    393   string salt_and_digest;
    394 
    395   SQLStatement query;
    396   query.prepare(dbhandle.get(),
    397                 "SELECT key, value FROM settings"
    398                 " WHERE email = ? AND (key = ? OR key = ?)");
    399   query.bind_string(0, email);
    400   query.bind_string(1, PASSWORD_HASH);
    401   query.bind_string(2, SALT);
    402   int query_result = query.step();
    403   string salt;
    404   int32 hash = kInvalidHash;
    405   while (SQLITE_ROW == query_result) {
    406     string key(query.column_string(0));
    407     if (key == SALT)
    408       salt = query.column_string(1);
    409     else
    410       hash = query.column_int(1);
    411     query_result = query.step();
    412   }
    413   CHECK(SQLITE_DONE == query_result);
    414   if (salt.empty() || hash == kInvalidHash)
    415     return false;
    416   MD5Calculator md5;
    417   md5.AddData(salt.data(), salt.size());
    418   md5.AddData(password.data(), password.size());
    419   return hash == GetHashFromDigest(md5.GetDigest());
    420 }
    421 
    422 void UserSettings::SwitchUser(const string& username) {
    423   {
    424     base::AutoLock lock(mutex_);
    425     email_ = username;
    426   }
    427 }
    428 
    429 string UserSettings::GetClientId() {
    430   ScopedDBHandle dbhandle(this);
    431   SQLStatement statement;
    432   statement.prepare(dbhandle.get(), "SELECT id FROM client_id");
    433   int query_result = statement.step();
    434   string client_id;
    435   if (query_result == SQLITE_ROW)
    436     client_id = statement.column_string(0);
    437   return client_id;
    438 }
    439 
    440 void UserSettings::ClearAllServiceTokens() {
    441   ScopedDBHandle dbhandle(this);
    442   ExecOrDie(dbhandle.get(), "DELETE FROM cookies");
    443 }
    444 
    445 bool UserSettings::GetLastUser(string* username) {
    446   ScopedDBHandle dbhandle(this);
    447   SQLStatement query;
    448   query.prepare(dbhandle.get(), "SELECT email FROM cookies");
    449   if (SQLITE_ROW == query.step()) {
    450     *username = query.column_string(0);
    451     return true;
    452   } else {
    453     return false;
    454   }
    455 }
    456 
    457 }  // namespace browser_sync
    458