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

Last change on this file since 15586 was 15586, checked in by mdewsnip, 16 years ago

(Adding new DB support) Changed userdbclass and keydbclass so they can use a general dbclass rather than specifically using gdbmclass.

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