source: main/trunk/greenstone2/runtime-src/src/recpt/userdb.cpp@ 21752

Last change on this file since 21752 was 21489, checked in by mdewsnip, 14 years ago

Modifying userdb.cpp so it uses the more general dbclass object instead of being hard-wired to use gdbmclass. However, this class is still set up to use gdbm, provided USE_GDBM is set (which it always is in the main Greenstone). The problem with the user database is it is Greenstone-specific, not collection-specific, so there is no infodbtype option to check to see what database type should be used (a new option will need to be created in the main.cfg file). If USE_GDBM isn't set, the userdb class will do nothing.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 16.8 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#ifdef USE_GDBM
33#include "gdbmclass.h"
34#endif
35
36// include crypt
37#if defined(__WIN32__)
38#include "crypt.h"
39#else
40#if defined(HAVE_CRYPT_H)
41#include <crypt.h>
42#else
43#include <unistd.h>
44#endif
45#endif
46
47//==========================================//
48// userinfo_t functions (Start) //
49//==========================================//
50userinfo_t::userinfo_t()
51{
52 clear();
53}
54
55userinfo_t::~userinfo_t(){};
56
57void userinfo_t::clear () {
58 username.clear();
59 password.clear();
60 enabled = false;
61 groups.clear();
62 comment.clear();
63}
64
65userinfo_t &userinfo_t::operator=(const userinfo_t &x) {
66 username = x.username;
67 password = x.password;
68 enabled = x.enabled;
69 groups = x.groups;
70 comment = x.comment;
71
72 return *this;
73}
74//==========================================//
75// userinfo_t functions (END) //
76//==========================================//
77
78//==========================================//
79// userdbclass functions (Start) //
80//==========================================//
81userdbclass::userdbclass(const text_t &userdbfilename)
82{
83 storeduserdbfilename = userdbfilename;
84
85 // Create a dbclass of the correct type
86 userdb = NULL;
87
88 // Use GDBM only at this stage
89#ifdef USE_GDBM
90 userdb = new gdbmclass();
91#endif
92
93 // Check a dbclass of some type has been created
94 if (userdb == NULL)
95 {
96 activated = false;
97 return;
98 }
99
100 activated = (!userdb->opendatabase(storeduserdbfilename, DB_READER, 1000, true)) ? false : true;
101 if (activated == false)
102 {
103 activated = (!userdb->opendatabase(storeduserdbfilename, DB_WRITER_CREATE, 1000, true)) ? false : true;
104 if (activated == true)
105 {
106 userdb->closedatabase();
107 activated = (!userdb->opendatabase(storeduserdbfilename, DB_READER, 1000, true)) ? false : true;
108 }
109 }
110
111 external_db = false;
112}
113
114userdbclass::~userdbclass()
115{
116 if (external_db == false)
117 {
118 userdb->closedatabase();
119 delete userdb;
120 }
121}
122
123// few useful functions
124text_t userdbclass::crypt_text (const text_t &text)
125{
126 static const char *salt = "Tp";
127 text_t crypt_password;
128
129 if (text.empty()) return g_EmptyText;
130
131 // encrypt the password
132 char *text_cstr = text.getcstr();
133 if (text_cstr == NULL) return g_EmptyText;
134 crypt_password = crypt(text_cstr, salt);
135 delete []text_cstr;
136
137 return crypt_password;
138}
139
140// username_ok tests to make sure a username is ok. a username
141// must be at least 2 characters long, but no longer than 30
142// characters long. it can contain the characters a-z A-Z 0-9
143// . and _
144bool userdbclass::username_ok (const text_t &username)
145{
146 if (username.size() < 2 || username.size() > 30) return false;
147
148 text_t::const_iterator here = username.begin();
149 text_t::const_iterator end = username.end();
150 while (here != end)
151 {
152 if ((*here >= 'a' && *here <= 'z') ||
153 (*here >= 'A' && *here <= 'Z') ||
154 (*here >= '0' && *here <= '9') ||
155 *here == '.' ||
156 *here == '_')
157 {
158 // ok
159 } else return false;
160 ++here;
161 }
162
163 return true;
164}
165
166// password_ok tests to make sure a password is ok. a password
167// must be at least 3 characters long but no longer than 8 characters
168// long. it can contain any character in the range 0x20-0x7e
169bool userdbclass::password_ok (const text_t &password)
170{
171 // DB: 20/02/08. Why mustn't the password exceed 8 chars,
172 // windows 3.1 complicance? Even then this doesn't make sense to me
173 // as storing this in GDBM wouldn't trigger a 8-char limit
174
175 // Have increased this to 128. Not because I think someone will type
176 // in something that long, but some encryptions schemes (e.g. wireless
177 // networks) work with such lengths and someone one day might like
178 // to use such a generated password in Greenstone.
179 //
180
181 if (password.size() < 3 || password.size() > 128) return false;
182
183 text_t::const_iterator here = password.begin();
184 text_t::const_iterator end = password.end();
185 while (here != end) {
186 if (*here >= 0x20 && *here <= 0x7e)
187 {
188 // ok
189 } else return false;
190 ++here;
191 }
192
193 return true;
194}
195
196// removes spaces from user groups
197text_t userdbclass::format_user_groups(const text_t user_groups)
198{
199 text_t new_groups = g_EmptyText;
200 text_t::const_iterator here = user_groups.begin();
201 text_t::const_iterator end = user_groups.end();
202 while (here != end) {
203 if (*here != ' '&& *here != '\t' && *here != '\n')
204 {
205 new_groups.push_back(*here);
206 }
207 ++here;
208 }
209 return new_groups;
210}
211
212// functions dealing with user databases
213// returns true on success (in which case userinfo will contain
214// the information for this user)
215// @return 0 success
216// @return -1 database is not currently connected
217// @return -2 not defined username
218int userdbclass::get_user_info (const text_t &username, userinfo_t &userinfo)
219{
220 // Let's make sure the connection has been established.
221 if (activated == true)
222 {
223 userinfo.clear();
224 infodbclass info;
225 // See if we can get the user's infomration
226 if (userdb->getinfo (username, info))
227 {
228 userinfo.username = info["username"];
229 userinfo.password = info["password"];
230 userinfo.enabled = (info["enabled"] == "true");
231 userinfo.groups = info["groups"];
232 userinfo.comment = info["comment"];
233 return ERRNO_SUCCEED;
234 }
235 // If we failed to retrieve the users information, we should check if the username is admin or not.
236 // If it is the admin user, let's create a new account for the admin user.
237 else if (username == "admin")
238 {
239 userinfo.clear();
240 userinfo.username = "admin";
241 userinfo.password = crypt_text("admin");
242 userinfo.enabled = true;
243 userinfo.groups = "administrator,all-collections-editor";
244 userinfo.comment = "change the password for this account as soon as possible";
245 // Return the set result.
246 return set_user_info (username, userinfo);
247 }
248 // The username is not found, return false
249 return ERRNO_USERNOTFOUND;
250 }
251 // Failed to connect to the database, return false.
252 return ERRNO_CONNECTIONFAILED;
253}
254
255// returns true on success
256int userdbclass::set_user_info (const text_t &username, const userinfo_t &userinfo)
257{
258 // Let's make sure the connection has been established.
259 if (activated == true)
260 {
261 infodbclass info;
262 info["username"] = userinfo.username;
263 info["password"] = userinfo.password;
264 info["enabled"] = userinfo.enabled ? "true" : "false";
265 info["groups"] = userinfo.groups;
266 info["comment"] = userinfo.comment;
267 userdb->closedatabase();
268 userdb->opendatabase(storeduserdbfilename, DB_WRITER_CREATE, 1000, true);
269 int result = (userdb->setinfo (username, info)) ? ERRNO_SUCCEED : ERRNO_DBACTIONFAILED;
270 userdb->closedatabase();
271 userdb->opendatabase(storeduserdbfilename, DB_READER, 1000, true);
272 return result;
273 }
274 return ERRNO_CONNECTIONFAILED;
275}
276
277// returns true if the user's password is correct.
278int userdbclass::check_passwd (const text_t &username, const text_t &password)
279{
280 userinfo_t thisuser;
281 int returned = get_user_info(username, thisuser);
282 if(returned != ERRNO_SUCCEED) return returned;
283 // couple of basic checks
284 if (thisuser.username.empty() || thisuser.password.empty() ||
285 password.empty()) return ERRNO_MISSINGPASSWORD;
286
287 text_t crypt_password = crypt_text(password);
288 return (thisuser.password == crypt_password) ? ERRNO_SUCCEED : ERRNO_PASSWORDMISMATCH;
289}
290
291int userdbclass::add_user (const userinfo_t &userinfo)
292{
293 // Let's make sure the connection has been established.
294 if (activated == true)
295 {
296 infodbclass info;
297 if (userdb->getinfo (userinfo.username, info))
298 {
299 // There is an existing username already
300 return ERRNO_EXISTINGUSERNAME;
301 }
302 else
303 {
304 return set_user_info(userinfo.username, userinfo);
305 }
306 }
307 return ERRNO_CONNECTIONFAILED;
308}
309
310int userdbclass::edit_user (const userinfo_t &userinfo)
311{
312 // Let's make sure the connection has been established.
313 if (activated == true)
314 {
315 infodbclass info;
316 if (userdb->getinfo (userinfo.username, info))
317 {
318 return set_user_info(userinfo.username, userinfo);
319 }
320 else
321 {
322 // The user does not exist in the database.
323 return ERRNO_USERNOTFOUND;
324 }
325 }
326 return ERRNO_CONNECTIONFAILED;
327}
328
329int userdbclass::delete_user (const text_t &username)
330{
331 // Let's make sure the connection has been established.
332 if (activated == true)
333 {
334 userdb->closedatabase();
335 userdb->opendatabase(storeduserdbfilename, DB_WRITER_CREATE, 1000, true);
336 userdb->deletekey (username);
337 userdb->closedatabase();
338 userdb->opendatabase(storeduserdbfilename, DB_READER, 1000, true);
339 return ERRNO_SUCCEED;
340 }
341 return ERRNO_CONNECTIONFAILED;
342}
343
344// gets all the users' information in the database. returns true
345// on success
346int userdbclass::get_all_users(userinfo_tarray &userinfo_array)
347{
348 // Let's make sure the connection has been established.
349 if (activated == true)
350 {
351 userinfo_array.erase(userinfo_array.begin(), userinfo_array.end());
352 text_tarray userlist = userdb->getkeys();
353 text_tarray::iterator user_iterator = userlist.begin();
354 while (user_iterator != userlist.end())
355 {
356 userinfo_t one_userinfo;
357 int returned = get_user_info(*user_iterator, one_userinfo);
358 if (returned != ERRNO_SUCCEED) return returned;
359 userinfo_array.push_back(one_userinfo);
360 user_iterator++;
361 }
362 return ERRNO_SUCCEED;
363 }
364 return ERRNO_CONNECTIONFAILED;
365}
366
367// gets a list of all the users in the database. returns true
368// on success
369int userdbclass::get_user_list (text_tarray &userlist)
370{
371 // Let's make sure the connection has been established.
372 if (activated == true)
373 {
374 userlist.erase (userlist.begin(), userlist.end());
375 userlist = userdb->getkeys();
376 return ERRNO_SUCCEED;
377 }
378 return ERRNO_CONNECTIONFAILED;
379}
380//==========================================//
381// userdbclass functions (End) //
382//==========================================//
383
384//==========================================//
385// keydbclass functions (Start) //
386//==========================================//
387keydbclass::keydbclass(const text_t &keydbfilename)
388{
389 storedkeydbfilename = keydbfilename;
390
391 // Create a dbclass of the correct type
392 keydb = NULL;
393
394 // Use GDBM only at this stage
395#ifdef USE_GDBM
396 keydb = new gdbmclass();
397#endif
398
399 // Check a dbclass of some type has been created
400 if (keydb == NULL)
401 {
402 activated = false;
403 return;
404 }
405
406 activated = keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
407 if (activated == false)
408 {
409 activated = keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
410 if (activated == true)
411 {
412 keydb->closedatabase();
413 activated = keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
414 }
415 }
416 external_db = false;
417}
418
419keydbclass::~keydbclass()
420{
421 if (external_db == false)
422 {
423 keydb->closedatabase();
424 delete keydb;
425 }
426}
427
428// generates a random key for the user, stores it in the database and
429// returns it so that it can be used in page generation
430// returns "" on failure
431text_t keydbclass::generate_key (const text_t &username)
432{
433 // Let's make sure the connection has been established.
434 if (activated == true)
435 {
436 static const char *numconvert = "0123456789abcdefghijklmnopqrstuvwxyz"
437 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
438
439 // loop looking for a suitable new key
440 text_t userkey;
441 text_t crypt_userkey;
442 do {
443 // convert to base 62 :-)
444 int userkey_int = rand ();
445 while (userkey_int > 0)
446 {
447 userkey.push_back (numconvert[userkey_int%62]);
448 userkey_int /= 62;
449 }
450
451 // make sure this key is not in the database
452 crypt_userkey = userdbclass::crypt_text(userkey);
453 if (keydb->exists (crypt_userkey)) userkey.clear();
454 } while (userkey.empty());
455
456 // enter the key into the database
457 infodbclass keydata;
458 keydata["user"] = username;
459 keydata["time"] = time2text(time(NULL));
460
461 keydb->closedatabase();
462 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
463 if (!keydb->setinfo (crypt_userkey, keydata))
464 {
465 userkey.clear(); // failed
466 }
467 keydb->closedatabase();
468 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
469
470 return userkey;
471 }
472 return "";
473}
474
475// checks to see if there is a key for this particular user in the
476// database that hasn't decayed. a short decay is used when group
477// is set to administrator
478bool keydbclass::check_key (const userinfo_t &thisuser, const text_t &key, const text_t &group, int keydecay)
479{
480 // Let's make sure the connection has been established.
481 if (activated == true)
482 {
483 if (thisuser.username.empty() || key.empty()) return false;
484
485 // the keydecay is set to 5 minute for things requiring the
486 // administrator
487 // if (group == "administrator") keydecay = 300;
488
489 // success if there is a key in the key database that is owned by this
490 // user whose creation time is less that keydecay
491 text_t crypt_key = userdbclass::crypt_text(key);
492 infodbclass info;
493 if (keydb->getinfo (crypt_key, info)) {
494 if (info["user"] == thisuser.username) {
495 time_t keycreation = text2time (info["time"]);
496 if (keycreation != (time_t)-1 && difftime (text2time(time2text(time(NULL))),
497 keycreation) <= keydecay) {
498 // succeeded, update the key's time
499 info["time"] = time2text(time(NULL));
500 keydb->closedatabase();
501 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
502 keydb->setinfo (crypt_key, info);
503 keydb->closedatabase();
504 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
505 return true;
506 }
507 }
508 }
509 }
510 return false;
511}
512
513// remove_old_keys will remove all keys created more than keydecay ago.
514// use sparingly, it can be quite an expensive function
515void keydbclass::remove_old_keys (int keydecay)
516{
517 // Let's make sure the connection has been established.
518 if (activated == true)
519 {
520 // get a list of keys created more than keydecay seconds agon
521 text_tarray oldkeys;
522 infodbclass info;
523 time_t timenow = text2time(time2text(time(NULL)));
524 time_t keycreation = (time_t)-1;
525
526 text_tarray keylist = keydb->getkeys();
527 text_tarray::iterator key_iterator = keylist.begin();
528 while (key_iterator != keylist.end())
529 {
530 if (keydb->getinfo (*key_iterator, info)) {
531 keycreation = text2time (info["time"]);
532 if (keycreation != (time_t)-1 && difftime (timenow, keycreation) > keydecay) {
533 // found an old key
534 oldkeys.push_back(*key_iterator);
535 }
536 }
537
538 key_iterator++;
539 }
540
541 // delete the old keys
542 if (oldkeys.size() > 0) {
543 keydb->closedatabase();
544 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
545 text_tarray::iterator keys_here = oldkeys.begin();
546 text_tarray::iterator keys_end = oldkeys.end();
547 while (keys_here != keys_end) {
548 keydb->deletekey(*keys_here);
549 ++keys_here;
550 }
551 keydb->closedatabase();
552 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
553 }
554 }
555}
556//==========================================//
557// keydbclass functions (End) //
558//==========================================//
Note: See TracBrowser for help on using the repository browser.