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

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