/********************************************************************** * * 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 // few useful functions text_t crypt_text (const text_t &text) { static const char *salt = "Tp"; text_t crypt_password; if (text.empty()) return ""; // encrypt the password char *text_cstr = text.getcstr(); if (text_cstr == NULL) return ""; 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 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 password_ok (const text_t &password) { if (password.size() < 3 || password.size() > 8) 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; } ///////////// // 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; } // functions dealing with user databases // returns true on success (in which case userinfo will contain // the information for this user) bool get_user_info (gdbmclass &userdb, const text_t &username, userinfo_t &userinfo) { userinfo.clear(); infodbclass info; 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 true; } return false; } bool get_user_info (const text_t &userdbfile, const text_t &username, userinfo_t &userinfo) { gdbmclass userdb; if (!userdb.opendatabase(userdbfile, GDBM_READER, 1000, true)) { /* if (!userdbfile.empty() && !file_exists (userdbfile) && username == "admin") {*/ if (!userdbfile.empty() && username == "admin") { // no database -- create a database with an initial account 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 set_user_info (userdbfile, username, userinfo); } return false; } bool success = get_user_info (userdb, username, userinfo); userdb.closedatabase(); return success; } // returns true on success bool set_user_info (gdbmclass &userdb, const text_t &username, const userinfo_t &userinfo) { infodbclass info; info["username"] = userinfo.username; info["password"] = userinfo.password; info["enabled"] = userinfo.enabled ? "true" : "false"; info["groups"] = userinfo.groups; info["comment"] = userinfo.comment; return userdb.setinfo (username, info); } bool set_user_info (const text_t &userdbfile, const text_t &username, const userinfo_t &userinfo) { gdbmclass userdb; if (!userdb.opendatabase(userdbfile, GDBM_WRCREAT, 1000, true)) return false; bool success = set_user_info (userdb, username, userinfo); userdb.closedatabase(); return success; } // removes a user from the user database -- forever void delete_user (gdbmclass &userdb, const text_t &username) { userdb.deletekey (username); } void delete_user (const text_t &userdbfile, const text_t &username) { gdbmclass userdb; if (!userdb.opendatabase(userdbfile, GDBM_WRCREAT, 1000, true)) return; delete_user (userdb, username); userdb.closedatabase(); } // gets a list of all the users in the database. returns true // on success void get_user_list (gdbmclass &userdb, text_tarray &userlist) { userlist.erase (userlist.begin(), userlist.end()); text_t user = userdb.getfirstkey (); while (!user.empty()) { userlist.push_back(user); user = userdb.getnextkey (user); } } // returns true if the user's password is correct. bool check_passwd (const userinfo_t &thisuser, const text_t &password) { // couple of basic checks if (thisuser.username.empty() || thisuser.password.empty() || password.empty()) return false; text_t crypt_password = crypt_text(password); return (thisuser.password == crypt_password); } // removes spaces from user groups text_t format_user_groups(const text_t user_groups){ text_t new_groups = ""; 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 databases of temporary keys // 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 generate_key (gdbmclass &keydb, const text_t &username) { 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 = 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)); if (!keydb.setinfo (crypt_userkey, keydata)) { userkey.clear(); // failed } return userkey; } text_t generate_key (const text_t &keydbfile, const text_t &username) { gdbmclass keydb; if (!keydb.opendatabase(keydbfile, GDBM_WRCREAT, 1000, true)) return ""; text_t key = generate_key (keydb, username); keydb.closedatabase(); return key; } // 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 check_key (gdbmclass &keydb, const userinfo_t &thisuser, const text_t &key, const text_t &group, int keydecay) { 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 = 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.setinfo (crypt_key, info); return true; } } } return false;; } bool check_key (const text_t &keydbfile, const userinfo_t &thisuser, const text_t &key, const text_t &group, int keydecay) { gdbmclass keydb; if (!keydb.opendatabase(keydbfile, GDBM_WRCREAT, 1000, true)) return false; bool success = check_key (keydb, thisuser, key, group, keydecay); keydb.closedatabase(); return success; } // remove_old_keys will remove all keys created more than keydecay ago. // use sparingly, it can be quite an expensive function void remove_old_keys (const text_t &keydbfile, int keydecay) { // open the key database gdbmclass keydb; if (!keydb.opendatabase(keydbfile, GDBM_WRCREAT, 1000, true)) return; // 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++; } // close the key database keydb.closedatabase(); }