/********************************************************************** * * 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. * * $Id: userdb.cpp 1183 2000-05-22 12:30:36Z sjboddie $ * *********************************************************************/ /* $Log$ Revision 1.7 2000/05/22 12:30:36 sjboddie the initial admin user now belongs to the colbuilder group by default (as well as the administrator group) Revision 1.6 2000/03/01 22:23:09 sjboddie tidied up windows installation Revision 1.5 2000/02/29 21:53:09 sjboddie tidied up crypt includes Revision 1.4 1999/09/07 04:57:00 sjboddie added GPL notice Revision 1.3 1999/09/02 00:30:04 rjmcnab added option for specifying whether the gdbm database should be locked Revision 1.2 1999/07/14 08:30:01 rjmcnab fixed an error to do with the way time is dealt with on different machines. It seems that there is no direct corresponding function to mktime and localtime or gmtime might not get you back to where you started. Revision 1.1 1999/07/13 23:22:04 rjmcnab Initial revision. */ #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); } // 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(); }