/********************************************************************** * * userdb.cpp -- functions to handle a user database * Copyright (C) 1999 DigiLib Systems Limited, New Zealand * * A component of the Greenstone digital library software * from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *********************************************************************/ #include "gsdlconf.h" #include "userdb.h" #include "gsdltimes.h" #include "fileutil.h" #include // include crypt #if defined(__WIN32__) #include "crypt.h" #else #if defined(HAVE_CRYPT_H) #include #else #include #endif #endif //==========================================// // userinfo_t functions (Start) // //==========================================// userinfo_t::userinfo_t() { clear(); } userinfo_t::~userinfo_t(){}; void userinfo_t::clear () { username.clear(); password.clear(); enabled = false; groups.clear(); comment.clear(); } userinfo_t &userinfo_t::operator=(const userinfo_t &x) { username = x.username; password = x.password; enabled = x.enabled; groups = x.groups; comment = x.comment; return *this; } //==========================================// // userinfo_t functions (END) // //==========================================// //==========================================// // userdbclass functions (Start) // //==========================================// userdbclass::userdbclass(const text_t &userdbfilename) { storeduserdbfilename = userdbfilename; activated = (!userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true)) ? false : true; if (activated == false) { activated = (!userdb.opendatabase(storeduserdbfilename, GDBM_WRCREAT, 1000, true)) ? false : true; if (activated == true) { userdb.closedatabase(); activated = (!userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true)) ? false : true; } } external_db = false; } userdbclass::userdbclass(const gdbmclass &external_userdb) { userdb = external_userdb; activated = true; external_db = true; } userdbclass::~userdbclass() { if (external_db == false) { userdb.closedatabase();} } // few useful functions text_t userdbclass::crypt_text (const text_t &text) { static const char *salt = "Tp"; text_t crypt_password; if (text.empty()) return g_EmptyText; // encrypt the password char *text_cstr = text.getcstr(); if (text_cstr == NULL) return g_EmptyText; crypt_password = crypt(text_cstr, salt); delete []text_cstr; return crypt_password; } // username_ok tests to make sure a username is ok. a username // must be at least 2 characters long, but no longer than 30 // characters long. it can contain the characters a-z A-Z 0-9 // . and _ bool userdbclass::username_ok (const text_t &username) { if (username.size() < 2 || username.size() > 30) return false; text_t::const_iterator here = username.begin(); text_t::const_iterator end = username.end(); while (here != end) { if ((*here >= 'a' && *here <= 'z') || (*here >= 'A' && *here <= 'Z') || (*here >= '0' && *here <= '9') || *here == '.' || *here == '_') { // ok } else return false; ++here; } return true; } // password_ok tests to make sure a password is ok. a password // must be at least 3 characters long but no longer than 8 characters // long. it can contain any character in the range 0x20-0x7e bool userdbclass::password_ok (const text_t &password) { // DB: 20/02/08. Why mustn't the password exceed 8 chars, // windows 3.1 complicance? Even then this doesn't make sense to me // as storing this in GDBM wouldn't trigger a 8-char limit // Have increased this to 128. Not because I think someone will type // in something that long, but some encryptions schemes (e.g. wireless // networks) work with such lengths and someone one day might like // to use such a generated password in Greenstone. // if (password.size() < 3 || password.size() > 128) return false; text_t::const_iterator here = password.begin(); text_t::const_iterator end = password.end(); while (here != end) { if (*here >= 0x20 && *here <= 0x7e) { // ok } else return false; ++here; } return true; } // removes spaces from user groups text_t userdbclass::format_user_groups(const text_t user_groups) { text_t new_groups = g_EmptyText; text_t::const_iterator here = user_groups.begin(); text_t::const_iterator end = user_groups.end(); while (here != end) { if (*here != ' '&& *here != '\t' && *here != '\n') { new_groups.push_back(*here); } ++here; } return new_groups; } // functions dealing with user databases // returns true on success (in which case userinfo will contain // the information for this user) // @return 0 success // @return -1 database is not currently connected // @return -2 not defined username int userdbclass::get_user_info (const text_t &username, userinfo_t &userinfo) { // Let's make sure the connection has been established. if (activated == true) { userinfo.clear(); infodbclass info; // See if we can get the user's infomration if (userdb.getinfo (username, info)) { userinfo.username = info["username"]; userinfo.password = info["password"]; userinfo.enabled = (info["enabled"] == "true"); userinfo.groups = info["groups"]; userinfo.comment = info["comment"]; return ERRNO_SUCCEED; } // If we failed to retrieve the users information, we should check if the username is admin or not. // If it is the admin user, let's create a new account for the admin user. else if (username == "admin") { userinfo.clear(); userinfo.username = "admin"; userinfo.password = crypt_text("admin"); userinfo.enabled = true; userinfo.groups = "administrator,colbuilder"; userinfo.comment = "change the password for this account as soon as possible"; // Return the set result. return set_user_info (username, userinfo); } // The username is not found, return false return ERRNO_USERNOTFOUND; } // Failed to connect to the database, return false. return ERRNO_CONNECTIONFAILED; } // returns true on success int userdbclass::set_user_info (const text_t &username, const userinfo_t &userinfo) { // Let's make sure the connection has been established. if (activated == true) { infodbclass info; info["username"] = userinfo.username; info["password"] = userinfo.password; info["enabled"] = userinfo.enabled ? "true" : "false"; info["groups"] = userinfo.groups; info["comment"] = userinfo.comment; userdb.closedatabase(); userdb.opendatabase(storeduserdbfilename, GDBM_WRCREAT, 1000, true); int result = (userdb.setinfo (username, info)) ? ERRNO_SUCCEED : ERRNO_GDBMACTIONFILED; userdb.closedatabase(); userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true); return result; } return ERRNO_CONNECTIONFAILED; } // returns true if the user's password is correct. int userdbclass::check_passwd (const text_t &username, const text_t &password) { userinfo_t thisuser; int returned = get_user_info(username, thisuser); if(returned != ERRNO_SUCCEED) return returned; // couple of basic checks if (thisuser.username.empty() || thisuser.password.empty() || password.empty()) return ERRNO_MISSINGPASSWORD; text_t crypt_password = crypt_text(password); return (thisuser.password == crypt_password) ? ERRNO_SUCCEED : ERRNO_PASSWORDMISMATCH; } int userdbclass::add_user (const userinfo_t &userinfo) { // Let's make sure the connection has been established. if (activated == true) { infodbclass info; if (userdb.getinfo (userinfo.username, info)) { // There is an existing username already return ERRNO_EXISTINGUSERNAME; } else { return set_user_info(userinfo.username, userinfo); } } return ERRNO_CONNECTIONFAILED; } int userdbclass::edit_user (const userinfo_t &userinfo) { // Let's make sure the connection has been established. if (activated == true) { infodbclass info; if (userdb.getinfo (userinfo.username, info)) { return set_user_info(userinfo.username, userinfo); } else { // The user does not exist in the database. return ERRNO_USERNOTFOUND; } } return ERRNO_CONNECTIONFAILED; } int userdbclass::delete_user (const text_t &username) { // Let's make sure the connection has been established. if (activated == true) { userdb.closedatabase(); userdb.opendatabase(storeduserdbfilename, GDBM_WRCREAT, 1000, true); userdb.deletekey (username); userdb.closedatabase(); userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true); return ERRNO_SUCCEED; } return ERRNO_CONNECTIONFAILED; } // gets all the users' information in the database. returns true // on success int userdbclass::get_all_users(userinfo_tarray &userinfo_array) { // Let's make sure the connection has been established. if (activated == true) { userinfo_array.erase(userinfo_array.begin(), userinfo_array.end()); text_t user = userdb.getfirstkey(); while (!user.empty()) { userinfo_t one_userinfo; int returned = get_user_info(user, one_userinfo); if (returned != ERRNO_SUCCEED) return returned; userinfo_array.push_back(one_userinfo); user = userdb.getnextkey(user); } return ERRNO_SUCCEED; } return ERRNO_CONNECTIONFAILED; } // gets a list of all the users in the database. returns true // on success int userdbclass::get_user_list (text_tarray &userlist) { // Let's make sure the connection has been established. if (activated == true) { userlist.erase (userlist.begin(), userlist.end()); text_t user = userdb.getfirstkey (); while (!user.empty()) { userlist.push_back(user); user = userdb.getnextkey (user); } return ERRNO_SUCCEED; } return ERRNO_CONNECTIONFAILED; } //==========================================// // userdbclass functions (End) // //==========================================// //==========================================// // keydbclass functions (Start) // //==========================================// keydbclass::keydbclass(const text_t &keydbfilename) { storedkeydbfilename = keydbfilename; activated = (!keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true)) ? false : true; if (activated == false) { activated = (!keydb.opendatabase(storedkeydbfilename, GDBM_WRCREAT, 1000, true)) ? false : true; if (activated == true) { keydb.closedatabase(); activated = (!keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true)) ? false : true; } } external_db = false; } keydbclass::keydbclass(const gdbmclass &external_keydb) { keydb = external_keydb; activated = true; external_db = true; } keydbclass::~keydbclass() { if (external_db == false) { keydb.closedatabase();} } // generates a random key for the user, stores it in the database and // returns it so that it can be used in page generation // returns "" on failure text_t keydbclass::generate_key (const text_t &username) { // Let's make sure the connection has been established. if (activated == true) { static const char *numconvert = "0123456789abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // loop looking for a suitable new key text_t userkey; text_t crypt_userkey; do { // convert to base 62 :-) int userkey_int = rand (); while (userkey_int > 0) { userkey.push_back (numconvert[userkey_int%62]); userkey_int /= 62; } // make sure this key is not in the database crypt_userkey = userdbclass::crypt_text(userkey); if (keydb.exists (crypt_userkey)) userkey.clear(); } while (userkey.empty()); // enter the key into the database infodbclass keydata; keydata["user"] = username; keydata["time"] = time2text(time(NULL)); keydb.closedatabase(); keydb.opendatabase(storedkeydbfilename, GDBM_WRCREAT, 1000, true); if (!keydb.setinfo (crypt_userkey, keydata)) { userkey.clear(); // failed } keydb.closedatabase(); keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true); return userkey; } return ""; } // checks to see if there is a key for this particular user in the // database that hasn't decayed. a short decay is used when group // is set to administrator bool keydbclass::check_key (const userinfo_t &thisuser, const text_t &key, const text_t &group, int keydecay) { // Let's make sure the connection has been established. if (activated == true) { if (thisuser.username.empty() || key.empty()) return false; // the keydecay is set to 5 minute for things requiring the // administrator // if (group == "administrator") keydecay = 300; // success if there is a key in the key database that is owned by this // user whose creation time is less that keydecay text_t crypt_key = userdbclass::crypt_text(key); infodbclass info; if (keydb.getinfo (crypt_key, info)) { if (info["user"] == thisuser.username) { time_t keycreation = text2time (info["time"]); if (keycreation != (time_t)-1 && difftime (text2time(time2text(time(NULL))), keycreation) <= keydecay) { // succeeded, update the key's time info["time"] = time2text(time(NULL)); keydb.closedatabase(); keydb.opendatabase(storedkeydbfilename, GDBM_WRCREAT, 1000, true); keydb.setinfo (crypt_key, info); keydb.closedatabase(); keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true); return true; } } } } return false; } // remove_old_keys will remove all keys created more than keydecay ago. // use sparingly, it can be quite an expensive function void keydbclass::remove_old_keys (int keydecay) { // Let's make sure the connection has been established. if (activated == true) { // get a list of keys created more than keydecay seconds agon text_tarray oldkeys; text_t key = keydb.getfirstkey (); infodbclass info; time_t timenow = text2time(time2text(time(NULL))); time_t keycreation = (time_t)-1; while (!key.empty()) { if (keydb.getinfo (key, info)) { keycreation = text2time (info["time"]); if (keycreation != (time_t)-1 && difftime (timenow, keycreation) > keydecay) { // found an old key oldkeys.push_back(key); } } key = keydb.getnextkey (key); } // delete the keys text_tarray::iterator keys_here = oldkeys.begin(); text_tarray::iterator keys_end = oldkeys.end(); while (keys_here != keys_end) { keydb.deletekey(*keys_here); ++keys_here; } } } //==========================================// // keydbclass functions (End) // //==========================================//