source: gsdl/trunk/src/recpt/userdb.cpp@ 14269

Last change on this file since 14269 was 14269, checked in by qq6, 14 years ago

Jeffrey has fixed operating records of users database on Windows

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 15.7 KB
Line 
1/**********************************************************************
2 *
3 * userdb.cpp -- functions to handle a user database
4 * Copyright (C) 1999 DigiLib Systems Limited, New Zealand
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include "gsdlconf.h"
27#include "userdb.h"
28#include "gsdltimes.h"
29#include "fileutil.h"
30#include <stdlib.h>
31
32// include crypt
33#if defined(__WIN32__)
34#include "crypt.h"
35#else
36#if defined(HAVE_CRYPT_H)
37#include <crypt.h>
38#else
39#include <unistd.h>
40#endif
41#endif
42
43//==========================================//
44// userinfo_t functions (Start) //
45//==========================================//
46userinfo_t::userinfo_t()
47{
48 clear();
49}
50
51userinfo_t::~userinfo_t(){};
52
53void userinfo_t::clear () {
54 username.clear();
55 password.clear();
56 enabled = false;
57 groups.clear();
58 comment.clear();
59}
60
61userinfo_t &userinfo_t::operator=(const userinfo_t &x) {
62 username = x.username;
63 password = x.password;
64 enabled = x.enabled;
65 groups = x.groups;
66 comment = x.comment;
67
68 return *this;
69}
70//==========================================//
71// userinfo_t functions (END) //
72//==========================================//
73
74//==========================================//
75// userdbclass functions (Start) //
76//==========================================//
77userdbclass::userdbclass(const text_t &userdbfilename)
78{
79 storeduserdbfilename = userdbfilename;
80 activated = (!userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true)) ? false : true;
81 if (activated == false)
82 {
83 activated = (!userdb.opendatabase(storeduserdbfilename, GDBM_WRCREAT, 1000, true)) ? false : true;
84 if (activated == true)
85 {
86 userdb.closedatabase();
87 activated = (!userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true)) ? false : true;
88 }
89 }
90
91 external_db = false;
92}
93
94userdbclass::userdbclass(const gdbmclass &external_userdb)
95{
96 userdb = external_userdb;
97 activated = true;
98 external_db = true;
99}
100
101userdbclass::~userdbclass()
102{
103 if (external_db == false) { userdb.closedatabase();}
104}
105
106// few useful functions
107text_t userdbclass::crypt_text (const text_t &text)
108{
109 static const char *salt = "Tp";
110 text_t crypt_password;
111
112 if (text.empty()) return g_EmptyText;
113
114 // encrypt the password
115 char *text_cstr = text.getcstr();
116 if (text_cstr == NULL) return g_EmptyText;
117 crypt_password = crypt(text_cstr, salt);
118 delete []text_cstr;
119
120 return crypt_password;
121}
122
123// username_ok tests to make sure a username is ok. a username
124// must be at least 2 characters long, but no longer than 30
125// characters long. it can contain the characters a-z A-Z 0-9
126// . and _
127bool userdbclass::username_ok (const text_t &username)
128{
129 if (username.size() < 2 || username.size() > 30) return false;
130
131 text_t::const_iterator here = username.begin();
132 text_t::const_iterator end = username.end();
133 while (here != end)
134 {
135 if ((*here >= 'a' && *here <= 'z') ||
136 (*here >= 'A' && *here <= 'Z') ||
137 (*here >= '0' && *here <= '9') ||
138 *here == '.' ||
139 *here == '_')
140 {
141 // ok
142 } else return false;
143 ++here;
144 }
145
146 return true;
147}
148
149// password_ok tests to make sure a password is ok. a password
150// must be at least 3 characters long but no longer than 8 characters
151// long. it can contain any character in the range 0x20-0x7e
152bool userdbclass::password_ok (const text_t &password)
153{
154 if (password.size() < 3 || password.size() > 8) return false;
155
156 text_t::const_iterator here = password.begin();
157 text_t::const_iterator end = password.end();
158 while (here != end) {
159 if (*here >= 0x20 && *here <= 0x7e)
160 {
161 // ok
162 } else return false;
163 ++here;
164 }
165
166 return true;
167}
168
169// removes spaces from user groups
170text_t userdbclass::format_user_groups(const text_t user_groups)
171{
172 text_t new_groups = g_EmptyText;
173 text_t::const_iterator here = user_groups.begin();
174 text_t::const_iterator end = user_groups.end();
175 while (here != end) {
176 if (*here != ' '&& *here != '\t' && *here != '\n')
177 {
178 new_groups.push_back(*here);
179 }
180 ++here;
181 }
182 return new_groups;
183}
184
185// functions dealing with user databases
186// returns true on success (in which case userinfo will contain
187// the information for this user)
188// @return 0 success
189// @return -1 database is not currently connected
190// @return -2 not defined username
191int userdbclass::get_user_info (const text_t &username, userinfo_t &userinfo)
192{
193 // Let's make sure the connection has been established.
194 if (activated == true)
195 {
196 userinfo.clear();
197 infodbclass info;
198 // See if we can get the user's infomration
199 if (userdb.getinfo (username, info))
200 {
201 userinfo.username = info["username"];
202 userinfo.password = info["password"];
203 userinfo.enabled = (info["enabled"] == "true");
204 userinfo.groups = info["groups"];
205 userinfo.comment = info["comment"];
206 return ERRNO_SUCCEED;
207 }
208 // If we failed to retrieve the users information, we should check if the username is admin or not.
209 // If it is the admin user, let's create a new account for the admin user.
210 else if (username == "admin")
211 {
212 userinfo.clear();
213 userinfo.username = "admin";
214 userinfo.password = crypt_text("admin");
215 userinfo.enabled = true;
216 userinfo.groups = "administrator,colbuilder";
217 userinfo.comment = "change the password for this account as soon as possible";
218 // Return the set result.
219 return set_user_info (username, userinfo);
220 }
221 // The username is not found, return false
222 return ERRNO_USERNOTFOUND;
223 }
224 // Failed to connect to the database, return false.
225 return ERRNO_CONNECTIONFAILED;
226}
227
228// returns true on success
229int userdbclass::set_user_info (const text_t &username, const userinfo_t &userinfo)
230{
231 // Let's make sure the connection has been established.
232 if (activated == true)
233 {
234 infodbclass info;
235 info["username"] = userinfo.username;
236 info["password"] = userinfo.password;
237 info["enabled"] = userinfo.enabled ? "true" : "false";
238 info["groups"] = userinfo.groups;
239 info["comment"] = userinfo.comment;
240 userdb.closedatabase();
241 userdb.opendatabase(storeduserdbfilename, GDBM_WRCREAT, 1000, true);
242 int result = (userdb.setinfo (username, info)) ? ERRNO_SUCCEED : ERRNO_GDBMACTIONFILED;
243 userdb.closedatabase();
244 userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true);
245 return result;
246 }
247 return ERRNO_CONNECTIONFAILED;
248}
249
250// returns true if the user's password is correct.
251int userdbclass::check_passwd (const text_t &username, const text_t &password)
252{
253 userinfo_t thisuser;
254 int returned = get_user_info(username, thisuser);
255 if(returned != ERRNO_SUCCEED) return returned;
256 // couple of basic checks
257 if (thisuser.username.empty() || thisuser.password.empty() ||
258 password.empty()) return ERRNO_MISSINGPASSWORD;
259
260 text_t crypt_password = crypt_text(password);
261 return (thisuser.password == crypt_password) ? ERRNO_SUCCEED : ERRNO_PASSWORDMISMATCH;
262}
263
264int userdbclass::add_user (const userinfo_t &userinfo)
265{
266 // Let's make sure the connection has been established.
267 if (activated == true)
268 {
269 infodbclass info;
270 if (userdb.getinfo (userinfo.username, info))
271 {
272 // There is an existing username already
273 return ERRNO_EXISTINGUSERNAME;
274 }
275 else
276 {
277 return set_user_info(userinfo.username, userinfo);
278 }
279 }
280 return ERRNO_CONNECTIONFAILED;
281}
282
283int userdbclass::edit_user (const userinfo_t &userinfo)
284{
285 // Let's make sure the connection has been established.
286 if (activated == true)
287 {
288 infodbclass info;
289 if (userdb.getinfo (userinfo.username, info))
290 {
291 return set_user_info(userinfo.username, userinfo);
292 }
293 else
294 {
295 // The user does not exist in the database.
296 return ERRNO_USERNOTFOUND;
297 }
298 }
299 return ERRNO_CONNECTIONFAILED;
300}
301
302int userdbclass::delete_user (const text_t &username)
303{
304 // Let's make sure the connection has been established.
305 if (activated == true)
306 {
307 userdb.closedatabase();
308 userdb.opendatabase(storeduserdbfilename, GDBM_WRCREAT, 1000, true);
309 userdb.deletekey (username);
310 userdb.closedatabase();
311 userdb.opendatabase(storeduserdbfilename, GDBM_READER, 1000, true);
312 return ERRNO_SUCCEED;
313 }
314 return ERRNO_CONNECTIONFAILED;
315}
316
317// gets all the users' information in the database. returns true
318// on success
319int userdbclass::get_all_users(userinfo_tarray &userinfo_array)
320{
321 // Let's make sure the connection has been established.
322 if (activated == true)
323 {
324 userinfo_array.erase(userinfo_array.begin(), userinfo_array.end());
325 text_t user = userdb.getfirstkey();
326 while (!user.empty()) {
327 userinfo_t one_userinfo;
328 int returned = get_user_info(user, one_userinfo);
329 if (returned != ERRNO_SUCCEED) return returned;
330 userinfo_array.push_back(one_userinfo);
331 user = userdb.getnextkey(user);
332 }
333 return ERRNO_SUCCEED;
334 }
335 return ERRNO_CONNECTIONFAILED;
336}
337
338// gets a list of all the users in the database. returns true
339// on success
340int userdbclass::get_user_list (text_tarray &userlist)
341{
342 // Let's make sure the connection has been established.
343 if (activated == true)
344 {
345 userlist.erase (userlist.begin(), userlist.end());
346
347 text_t user = userdb.getfirstkey ();
348 while (!user.empty()) {
349 userlist.push_back(user);
350 user = userdb.getnextkey (user);
351 }
352 return ERRNO_SUCCEED;
353 }
354 return ERRNO_CONNECTIONFAILED;
355}
356//==========================================//
357// userdbclass functions (End) //
358//==========================================//
359
360//==========================================//
361// keydbclass functions (Start) //
362//==========================================//
363keydbclass::keydbclass(const text_t &keydbfilename)
364{
365 storedkeydbfilename = keydbfilename;
366 activated = (!keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true)) ? false : true;
367 if (activated == false)
368 {
369 activated = (!keydb.opendatabase(storedkeydbfilename, GDBM_WRCREAT, 1000, true)) ? false : true;
370 if (activated == true)
371 {
372 keydb.closedatabase();
373 activated = (!keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true)) ? false : true;
374 }
375 }
376 external_db = false;
377}
378
379keydbclass::keydbclass(const gdbmclass &external_keydb)
380{
381 keydb = external_keydb;
382 activated = true;
383 external_db = true;
384}
385
386keydbclass::~keydbclass()
387{
388 if (external_db == false) { keydb.closedatabase();}
389}
390// generates a random key for the user, stores it in the database and
391// returns it so that it can be used in page generation
392// returns "" on failure
393text_t keydbclass::generate_key (const text_t &username)
394{
395 // Let's make sure the connection has been established.
396 if (activated == true)
397 {
398 static const char *numconvert = "0123456789abcdefghijklmnopqrstuvwxyz"
399 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
400
401 // loop looking for a suitable new key
402 text_t userkey;
403 text_t crypt_userkey;
404 do {
405 // convert to base 62 :-)
406 int userkey_int = rand ();
407 while (userkey_int > 0)
408 {
409 userkey.push_back (numconvert[userkey_int%62]);
410 userkey_int /= 62;
411 }
412
413 // make sure this key is not in the database
414 crypt_userkey = userdbclass::crypt_text(userkey);
415 if (keydb.exists (crypt_userkey)) userkey.clear();
416 } while (userkey.empty());
417
418 // enter the key into the database
419 infodbclass keydata;
420 keydata["user"] = username;
421 keydata["time"] = time2text(time(NULL));
422
423 keydb.closedatabase();
424 keydb.opendatabase(storedkeydbfilename, GDBM_WRCREAT, 1000, true);
425 if (!keydb.setinfo (crypt_userkey, keydata))
426 {
427 userkey.clear(); // failed
428 }
429 keydb.closedatabase();
430 keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true);
431
432 return userkey;
433 }
434 return "";
435}
436
437// checks to see if there is a key for this particular user in the
438// database that hasn't decayed. a short decay is used when group
439// is set to administrator
440bool keydbclass::check_key (const userinfo_t &thisuser, const text_t &key, const text_t &group, int keydecay)
441{
442 // Let's make sure the connection has been established.
443 if (activated == true)
444 {
445 if (thisuser.username.empty() || key.empty()) return false;
446
447 // the keydecay is set to 5 minute for things requiring the
448 // administrator
449 // if (group == "administrator") keydecay = 300;
450
451 // success if there is a key in the key database that is owned by this
452 // user whose creation time is less that keydecay
453 text_t crypt_key = userdbclass::crypt_text(key);
454 infodbclass info;
455 if (keydb.getinfo (crypt_key, info)) {
456 if (info["user"] == thisuser.username) {
457 time_t keycreation = text2time (info["time"]);
458 if (keycreation != (time_t)-1 && difftime (text2time(time2text(time(NULL))),
459 keycreation) <= keydecay) {
460 // succeeded, update the key's time
461 info["time"] = time2text(time(NULL));
462 keydb.closedatabase();
463 keydb.opendatabase(storedkeydbfilename, GDBM_WRCREAT, 1000, true);
464 keydb.setinfo (crypt_key, info);
465 keydb.closedatabase();
466 keydb.opendatabase(storedkeydbfilename, GDBM_READER, 1000, true);
467 return true;
468 }
469 }
470 }
471 }
472 return false;
473}
474
475// remove_old_keys will remove all keys created more than keydecay ago.
476// use sparingly, it can be quite an expensive function
477void keydbclass::remove_old_keys (int keydecay)
478{
479 // Let's make sure the connection has been established.
480 if (activated == true)
481 {
482 // get a list of keys created more than keydecay seconds agon
483 text_tarray oldkeys;
484 text_t key = keydb.getfirstkey ();
485 infodbclass info;
486 time_t timenow = text2time(time2text(time(NULL)));
487 time_t keycreation = (time_t)-1;
488 while (!key.empty()) {
489 if (keydb.getinfo (key, info)) {
490 keycreation = text2time (info["time"]);
491 if (keycreation != (time_t)-1 && difftime (timenow, keycreation) > keydecay) {
492 // found an old key
493 oldkeys.push_back(key);
494 }
495 }
496
497 key = keydb.getnextkey (key);
498 }
499
500 // delete the keys
501 text_tarray::iterator keys_here = oldkeys.begin();
502 text_tarray::iterator keys_end = oldkeys.end();
503 while (keys_here != keys_end) {
504 keydb.deletekey(*keys_here);
505 ++keys_here;
506 }
507 }
508}
509//==========================================//
510// keydbclass functions (End) //
511//==========================================//
Note: See TracBrowser for help on using the repository browser.