srv-users.cpp

Go to the documentation of this file.
00001 /*
00002  * srv-users.cpp
00003  *
00004  * Copyright (C) 2008  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  * User/Player management.  See srv-users.h
00031  */
00032 
00033 // includes -------------------------------------------------------------------
00034 #include "srv-users.h"                  // always include our own header first!
00035 
00036 #include <dirent.h>
00037 
00038 #include "common/wave_ex.h"
00039 #include "crypto/crypto.h"
00040 #include "datahash/datahash_text.h"
00041 #include "datahash/datahash_util.h"
00042 #include "perf/perf.h"
00043 #include "threadsafe/threadsafe_map.h"
00044 #include "util/file.h"
00045 
00046 
00047 namespace aesop {
00048 
00049 
00050 UserManager::~UserManager(void) throw() { }
00051 
00052 
00053 // min/max username.  Max is interesting!  It should be capable of 1e9
00054 // usable names at least.  The limit of 15 was chosen so that a static char
00055 // buffer (with null terminator) could be held in 16 bytes.
00056 static const int s_minUsernameLength            = 3;
00057 static const int s_maxUsernameLength            = 15;
00058 
00059 
00060 static const char * s_configFilename            = "user-db.config";
00061 
00062 
00063 
00065 //
00066 //      UserMgr -- object that implements the UserManager interface
00067 //
00069 
00070 class UserMgr : public UserManager {
00071 public:
00072         UserMgr(void) throw();
00073         ~UserMgr(void) throw() { }
00074 
00075         // public class methods ------------------------------------------------
00076         void initialize(IN const Datahash * params);
00077 
00078         // aesop::UserManager class interface methods -------------------------
00079         bool canCreateNewUsersTS(void) const throw() { return m_canCreateNewUsers; }
00080         bool passwordRequiredTS(void) const throw() { return m_passwordRequired; }
00081         void getUsernamesTS(OUT SetString& names);
00082         bool logInAsUserTS(IN const char * username,
00083                                 IN const char * password,
00084                                 OUT std::string& playerGuid,
00085                                 OUT std::string& diagnostic);
00086         bool createUserTS(IN const char * username,
00087                                 OUT std::string& diagnostic);
00088         bool isAdminTS(IN const char * username);
00089 
00090 private:
00091         // private typedefs ----------------------------------------------------
00092         typedef threadsafe_map<std::string, long> user_map_t;
00093 
00094         // private helper methods ----------------------------------------------
00095         void getUserPath(IN const char * username,
00096                                 OUT std::string& path);
00097 
00098         // private member data -------------------------------------------------
00099         std::string                     m_userDir;
00100         user_map_t                      m_userMap;
00101         smart_mutex                     m_mutex;
00102         bool                            m_canCreateNewUsers;
00103         bool                            m_passwordRequired;
00104 };
00105 
00106 
00107 
00108 UserMgr::UserMgr(void)
00109 throw()
00110 {
00111 }
00112 
00113 
00114 
00115 void
00116 UserMgr::initialize
00117 (
00118 IN const Datahash * params
00119 )
00120 {
00121         ASSERT(params, "null");
00122 
00123         m_userDir = getString(params, "userDir");
00124         DPRINTF("Using user directory = %s", m_userDir.c_str());
00125 
00126         // read config file for local user database
00127         std::string configFile = m_userDir;
00128         configFile += "/";
00129         configFile += s_configFilename;
00130 
00131         DPRINTF("  Loading user db policies from file: %s", configFile.c_str());
00132 
00133         smart_ptr<Datahash> config = readHashFromTextFile(configFile.c_str());
00134         ASSERT(config, "null");
00135 
00136         // determine policies
00137         m_canCreateNewUsers = getBoolean(config, "canCreateNewUsers");
00138         m_passwordRequired = getBoolean(config, "passwordRequired");
00139 }
00140 
00141 
00142 
00144 //
00145 //      UserMgr -- aesop::UserManager class interface methods
00146 //
00148 
00149 void
00150 UserMgr::getUsernamesTS
00151 (
00152 OUT SetString& names
00153 )
00154 {
00155         perf::Timer timer("UserManager::getUsernames");
00156         // threadsafe!
00157 
00158         names.clear();          // clear before returning
00159 
00160         // we read the directory from scratch each time
00161         DIR * d = opendir(m_userDir.c_str());
00162         if (!d) {
00163                 WAVE_EX(wex);
00164                 wex << "Failed to open user directory for reading: ";
00165                 wex << m_userDir;
00166         }
00167 
00168         // parse entries
00169         while (dirent * entry = readdir(d)) {
00170                 if ('.' == entry->d_name[0])
00171                         continue;       // skip entries beginning with '.' 
00172 
00173                 if (strcmp("user", GetExtension(entry->d_name)))
00174                         continue;       // must end in ".user"
00175 
00176                 std::string login;
00177                 GetFileRoot(entry->d_name, login);
00178                 if (!isValidUsername(login.c_str())) {
00179                         DPRINTF("Invalid username: %s", login.c_str());
00180                         continue;       // skip invalid
00181                 }
00182 
00183                 // okay!
00184                 names.insert(login);
00185         }
00186 }
00187 
00188 
00189 
00190 bool
00191 UserMgr::logInAsUserTS
00192 (
00193 IN const char * username,
00194 IN const char * password,
00195 OUT std::string& playerGuid,    
00196 OUT std::string& diagnostic     
00197 )
00198 {
00199         perf::Timer timer("UserManager::logInAsUser");
00200         ASSERT(username, "null");
00201         ASSERT(password, "null");
00202         playerGuid = "";
00203         diagnostic = "";
00204 
00205         if (!isValidUsername(username)) {
00206                 diagnostic = "Username has invalid format";
00207                 return false;
00208         }
00209 
00210         // construct filename
00211         std::string filename;
00212         this->getUserPath(username, filename);
00213 
00214         // read user file (throws if user does not exist)
00215         smart_ptr<Datahash> hash = readHashFromTextFile(filename.c_str());
00216         ASSERT(hash, "null");
00217 
00218         // require a password?
00219         if (this->passwordRequiredTS()) {
00220                 // do we have a password?
00221                 if (!password || !*password) {
00222                         diagnostic = "Password required but none provided?";
00223                         return false;
00224                 }
00225 
00226                 // must hash username + password
00227                 // (this way two users with the same password won't realize it)
00228                 std::string to_hash = username;
00229                 to_hash += ":";
00230                 to_hash += password;
00231 
00232                 std::string in_sha1 = crypto::getSHA1(to_hash.c_str());
00233                 // DPRINTF("hashed password: %s", in_sha1.c_str());
00234 
00235                 // does user file contain password?
00236                 if (hash->count("password")) {
00237                         std::string file_pw = getString(hash, "password");
00238                         if (file_pw != in_sha1) {
00239                                 diagnostic = "Invalid password";
00240                                 return false;
00241                         }
00242                         // passwords match!
00243                 } else {
00244                         // user file does not contain password!
00245                         DPRINTF("Setting password for user: %s", username);
00246                         hash->insert("password", in_sha1.c_str());
00247                         writeHashToTextFile(hash, filename.c_str());
00248                 }
00249         }
00250 
00251         // got here?  Logged in fine!  Create player GUID
00252         mlock lock(m_mutex);    // lock it down--messing with ref counting
00253 
00254         long count;
00255         if (!m_userMap.lookup(username, count)) {
00256                 count = 0;
00257         } else {
00258                 ++count;
00259         }
00260         DPRINTF("Count for '%s': %ld", username, count);
00261         playerGuid = username;
00262         if (count) {
00263                 char buffer[64];
00264                 sprintf(buffer, "%ld", count);
00265                 playerGuid += buffer;
00266         }
00267         m_userMap.insert(username, count);
00268 
00269         DPRINTF("Tag: %s", playerGuid.c_str());
00270 
00271         return true;
00272 }
00273 
00274 
00275 
00276 bool
00277 UserMgr::createUserTS
00278 (
00279 IN const char * username,
00280 OUT std::string& diagnostic
00281 )
00282 {
00283         perf::Timer timer("UserManager::createUser");
00284         ASSERT(username, "null");
00285         diagnostic = "";
00286 
00287         // valid username?
00288         if (!isValidUsername(username)) {
00289                 diagnostic = getUsernameRestrictions();
00290                 return false;
00291         }
00292 
00293         // construct path
00294         std::string path;
00295         this->getUserPath(username, path);
00296 
00297         // verify does not exist
00298         if (doesPathExist(path.c_str())) {
00299                 diagnostic = "Username '";
00300                 diagnostic += username;
00301                 diagnostic += "' already exists.";
00302                 return false;
00303         }
00304 
00305         // create!
00306         createEmptyFileIfDoesNotExist(path.c_str());
00307         return true;
00308 }
00309 
00310 
00311 
00312 bool
00313 UserMgr::isAdminTS
00314 (
00315 IN const char * username
00316 )
00317 {
00318         ASSERT(username, "null");
00319 
00320         std::string path;
00321         this->getUserPath(username, path);
00322 
00323         smart_ptr<Datahash> hash = readHashFromTextFile(path.c_str());
00324         ASSERT(hash, "null");
00325 
00326         std::string isAdmin = getOptionalString(hash, "isAdmin", "false");
00327         return getBooleanValueFromString(isAdmin.c_str());
00328 }
00329 
00330 
00331 
00333 //
00334 //      UserMgr -- private helper methods
00335 //
00337 
00338 void
00339 UserMgr::getUserPath
00340 (
00341 IN const char * username,
00342 OUT std::string& path
00343 )
00344 {
00345         ASSERT(username, "null");
00346 
00347         path = m_userDir;
00348         path += "/";
00349         path += username;
00350         path += ".user";
00351 }
00352 
00353 
00354 
00356 //
00357 //      public API
00358 //
00360 
00361 smart_ptr<UserManager>
00362 UserManager::create
00363 (
00364 IN const Datahash * params
00365 )
00366 {
00367         ASSERT(params, "null");
00368 
00369         smart_ptr<UserMgr> local = new UserMgr;
00370         ASSERT(local, "out of memory");
00371 
00372         local->initialize(params);
00373 
00374         return local;
00375 }
00376 
00377 
00378 
00379 bool
00380 isValidUsername
00381 (
00382 IN const char * username
00383 )
00384 {
00385         ASSERT(username, "null");
00386 
00387         int len = strlen(username);
00388         if (len < s_minUsernameLength || len > s_maxUsernameLength) {
00389                 return false;
00390         }
00391 
00392         for (; *username; ++username) {
00393                 if (*username < 'a' || *username > 'z')
00394                         return false;
00395         }
00396 
00397         return true;
00398 }
00399 
00400 
00401 
00402 std::string
00403 getUsernameRestrictions
00404 (
00405 void
00406 )
00407 {
00408         std::ostringstream oss;
00409         oss << "Usernames can contain only lower-case characters, and must be ";
00410         oss << s_minUsernameLength << " to " << s_maxUsernameLength;
00411         oss << " characters long.";
00412 
00413         return oss.str();
00414 }
00415 
00416 
00417 
00418 };      // aesop namespace
00419