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

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

(Adding new DB support) Replaced the "getfirstkey()" and "getnextkey()" functions in dbclass with "getkeys()" (which returns them all). These functions were only used in userdb.cpp, and having the one function is simpler without being (much) less efficient. Also, implementing getkeys() for the new sql db classes will be easier than getfirstkey() and getnextkey().

  • 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_tarray userlist = userdb->getkeys();
335 text_tarray::iterator user_iterator = userlist.begin();
336 while (user_iterator != userlist.end())
337 {
338 userinfo_t one_userinfo;
339 int returned = get_user_info(*user_iterator, one_userinfo);
340 if (returned != ERRNO_SUCCEED) return returned;
341 userinfo_array.push_back(one_userinfo);
342 user_iterator++;
343 }
344 return ERRNO_SUCCEED;
345 }
346 return ERRNO_CONNECTIONFAILED;
347}
348
349// gets a list of all the users in the database. returns true
350// on success
351int userdbclass::get_user_list (text_tarray &userlist)
352{
353 // Let's make sure the connection has been established.
354 if (activated == true)
355 {
356 userlist.erase (userlist.begin(), userlist.end());
357 userlist = userdb->getkeys();
358 return ERRNO_SUCCEED;
359 }
360 return ERRNO_CONNECTIONFAILED;
361}
362//==========================================//
363// userdbclass functions (End) //
364//==========================================//
365
366//==========================================//
367// keydbclass functions (Start) //
368//==========================================//
369keydbclass::keydbclass(const text_t &keydbfilename)
370{
371 storedkeydbfilename = keydbfilename;
372 keydb = new gdbmclass();
373 activated = keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
374 if (activated == false)
375 {
376 activated = keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
377 if (activated == true)
378 {
379 keydb->closedatabase();
380 activated = keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
381 }
382 }
383 external_db = false;
384}
385
386keydbclass::~keydbclass()
387{
388 if (external_db == false)
389 {
390 keydb->closedatabase();
391 delete keydb;
392 }
393}
394
395// generates a random key for the user, stores it in the database and
396// returns it so that it can be used in page generation
397// returns "" on failure
398text_t keydbclass::generate_key (const text_t &username)
399{
400 // Let's make sure the connection has been established.
401 if (activated == true)
402 {
403 static const char *numconvert = "0123456789abcdefghijklmnopqrstuvwxyz"
404 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
405
406 // loop looking for a suitable new key
407 text_t userkey;
408 text_t crypt_userkey;
409 do {
410 // convert to base 62 :-)
411 int userkey_int = rand ();
412 while (userkey_int > 0)
413 {
414 userkey.push_back (numconvert[userkey_int%62]);
415 userkey_int /= 62;
416 }
417
418 // make sure this key is not in the database
419 crypt_userkey = userdbclass::crypt_text(userkey);
420 if (keydb->exists (crypt_userkey)) userkey.clear();
421 } while (userkey.empty());
422
423 // enter the key into the database
424 infodbclass keydata;
425 keydata["user"] = username;
426 keydata["time"] = time2text(time(NULL));
427
428 keydb->closedatabase();
429 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
430 if (!keydb->setinfo (crypt_userkey, keydata))
431 {
432 userkey.clear(); // failed
433 }
434 keydb->closedatabase();
435 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
436
437 return userkey;
438 }
439 return "";
440}
441
442// checks to see if there is a key for this particular user in the
443// database that hasn't decayed. a short decay is used when group
444// is set to administrator
445bool keydbclass::check_key (const userinfo_t &thisuser, const text_t &key, const text_t &group, int keydecay)
446{
447 // Let's make sure the connection has been established.
448 if (activated == true)
449 {
450 if (thisuser.username.empty() || key.empty()) return false;
451
452 // the keydecay is set to 5 minute for things requiring the
453 // administrator
454 // if (group == "administrator") keydecay = 300;
455
456 // success if there is a key in the key database that is owned by this
457 // user whose creation time is less that keydecay
458 text_t crypt_key = userdbclass::crypt_text(key);
459 infodbclass info;
460 if (keydb->getinfo (crypt_key, info)) {
461 if (info["user"] == thisuser.username) {
462 time_t keycreation = text2time (info["time"]);
463 if (keycreation != (time_t)-1 && difftime (text2time(time2text(time(NULL))),
464 keycreation) <= keydecay) {
465 // succeeded, update the key's time
466 info["time"] = time2text(time(NULL));
467 keydb->closedatabase();
468 keydb->opendatabase(storedkeydbfilename, DB_WRITER_CREATE, 1000, true);
469 keydb->setinfo (crypt_key, info);
470 keydb->closedatabase();
471 keydb->opendatabase(storedkeydbfilename, DB_READER, 1000, true);
472 return true;
473 }
474 }
475 }
476 }
477 return false;
478}
479
480// remove_old_keys will remove all keys created more than keydecay ago.
481// use sparingly, it can be quite an expensive function
482void keydbclass::remove_old_keys (int keydecay)
483{
484 // Let's make sure the connection has been established.
485 if (activated == true)
486 {
487 // get a list of keys created more than keydecay seconds agon
488 text_tarray oldkeys;
489 infodbclass info;
490 time_t timenow = text2time(time2text(time(NULL)));
491 time_t keycreation = (time_t)-1;
492
493 text_tarray keylist = keydb->getkeys();
494 text_tarray::iterator key_iterator = keylist.begin();
495 while (key_iterator != keylist.end())
496 {
497 if (keydb->getinfo (*key_iterator, 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_iterator);
502 }
503 }
504
505 key_iterator++;
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.