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

Last change on this file since 30465 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
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
33#if defined(USE_GDBM)
34#include "gdbmclass.h"
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."
59#endif
60
61
62// include crypt
63#if defined(__WIN32__)
64#include "crypt.h"
65#else
66#if defined(HAVE_CRYPT_H)
67#include <crypt.h>
68#else
69#include <unistd.h>
70#endif
71#endif
72
73
74//==========================================//
75// userinfo_t functions (Start) //
76//==========================================//
77userinfo_t::userinfo_t()
78{
79 clear();
80}
81
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//==========================================//
108userdbclass::userdbclass(const text_t &gsdlhome)
109{
110
111 storeduserdbfilename = filename_cat(gsdlhome, "etc", USERDBFNAME);
112
113 // Create a dbclass of the correct type
114 userdb = NULL;
115
116 // Use the correct database type
117 userdb = new DBCLASS(gsdlhome);
118
119 // Check a dbclass of some type has been created
120 if (userdb == NULL)
121 {
122 activated = false;
123 return;
124 }
125
126 activated = (!userdb->opendatabase(storeduserdbfilename, DB_READER, 1000, true)) ? false : true;
127 if (activated == false)
128 {
129 activated = (!userdb->opendatabase(storeduserdbfilename, DB_WRITER_CREATE, 1000, true)) ? false : true;
130 if (activated == true)
131 {
132 userdb->closedatabase();
133 activated = (!userdb->opendatabase(storeduserdbfilename, DB_READER, 1000, true)) ? false : true;
134 }
135 }
136
137 external_db = false;
138}
139
140userdbclass::~userdbclass()
141{
142 if (external_db == false)
143 {
144 userdb->closedatabase();
145 delete userdb;
146 }
147}
148
149// few useful functions
150text_t userdbclass::crypt_text (const text_t &text)
151{
152 static const char *salt = "Tp";
153 text_t crypt_password;
154
155 if (text.empty()) return g_EmptyText;
156
157 // encrypt the password
158 char *text_cstr = text.getcstr();
159 if (text_cstr == NULL) return g_EmptyText;
160 crypt_password = crypt(text_cstr, salt);
161 delete []text_cstr;
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 _
170bool userdbclass::username_ok (const text_t &username)
171{
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();
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
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
195bool userdbclass::password_ok (const text_t &password)
196{
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
200
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
209 text_t::const_iterator here = password.begin();
210 text_t::const_iterator end = password.end();
211 while (here != end) {
212 if (*here >= 0x20 && *here <= 0x7e)
213 {
214 // ok
215 } else return false;
216 ++here;
217 }
218
219 return true;
220}
221
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;
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)
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 {
249 userinfo.clear();
250 infodbclass info;
251 // See if we can get the user's infomration
252 if (userdb->getinfo (username, info))
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;
269 userinfo.groups = "administrator,all-collections-editor";
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;
276 }
277 // Failed to connect to the database, return false.
278 return ERRNO_CONNECTIONFAILED;
279}
280
281// returns true on success
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;
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);
298 return result;
299 }
300 return ERRNO_CONNECTIONFAILED;
301}
302
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;
312
313 text_t crypt_password = crypt_text(password);
314 return (thisuser.password == crypt_password) ? ERRNO_SUCCEED : ERRNO_PASSWORDMISMATCH;
315}
316
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;
323 if (userdb->getinfo (userinfo.username, info))
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;
334}
335
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;
342 if (userdb->getinfo (userinfo.username, info))
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;
353}
354
355int userdbclass::delete_user (const text_t &username)
356{
357 // Let's make sure the connection has been established.
358 if (activated == true)
359 {
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);
365 return ERRNO_SUCCEED;
366 }
367 return ERRNO_CONNECTIONFAILED;
368}
369
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());
378 text_tarray userlist = userdb->getkeys();
379 text_tarray::iterator user_iterator = userlist.begin();
380 while (user_iterator != userlist.end())
381 {
382 userinfo_t one_userinfo;
383 int returned = get_user_info(*user_iterator, one_userinfo);
384 if (returned != ERRNO_SUCCEED) return returned;
385 userinfo_array.push_back(one_userinfo);
386 user_iterator++;
387 }
388 return ERRNO_SUCCEED;
389 }
390 return ERRNO_CONNECTIONFAILED;
391}
392
393// gets a list of all the users in the database. returns true
394// on success
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());
401 userlist = userdb->getkeys();
402 return ERRNO_SUCCEED;
403 }
404 return ERRNO_CONNECTIONFAILED;
405}
406//==========================================//
407// userdbclass functions (End) //
408//==========================================//
409
410//==========================================//
411// keydbclass functions (Start) //
412//==========================================//
413keydbclass::keydbclass(const text_t &gsdlhome)
414{
415 storedkeydbfilename = filename_cat(gsdlhome, "etc", KEYDBFNAME);
416
417 // Create a dbclass of the correct type
418 keydb = NULL;
419
420 // Use the correct DB class type at this stage
421 keydb = new DBCLASS(gsdlhome);
422
423 // Check a dbclass of some type has been created
424 if (keydb == NULL)
425 {
426 activated = false;
427 return;
428 }
429
430 activated = keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
431 if (activated == false)
432 {
433 activated = keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
434 if (activated == true)
435 {
436 keydb->closedatabase();
437 activated = keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
438 }
439 }
440 external_db = false;
441}
442
443keydbclass::~keydbclass()
444{
445 if (external_db == false)
446 {
447 keydb->closedatabase();
448 delete keydb;
449 }
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
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);
477 if (keydb->exists (crypt_userkey)) userkey.clear();
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
485 keydb->closedatabase();
486 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
487 if (!keydb->setinfo (crypt_userkey, keydata))
488 {
489 userkey.clear(); // failed
490 }
491 keydb->closedatabase();
492 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
493
494 return userkey;
495 }
496 return "";
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
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;
517 if (keydb->getinfo (crypt_key, info)) {
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));
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);
529 return true;
530 }
531 }
532 }
533 }
534 return false;
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
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;
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)) {
555 keycreation = text2time (info["time"]);
556 if (keycreation != (time_t)-1 && difftime (timenow, keycreation) > keydecay) {
557 // found an old key
558 oldkeys.push_back(*key_iterator);
559 }
560 }
561
562 key_iterator++;
563 }
564
565 // delete the old keys
566 if (oldkeys.size() > 0) {
567 keydb->closedatabase();
568 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
569 text_tarray::iterator keys_here = oldkeys.begin();
570 text_tarray::iterator keys_end = oldkeys.end();
571 while (keys_here != keys_end) {
572 keydb->deletekey(*keys_here);
573 ++keys_here;
574 }
575 keydb->closedatabase();
576 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
577 }
578 }
579}
580//==========================================//
581// keydbclass functions (End) //
582//==========================================//
Note: See TracBrowser for help on using the repository browser.