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

Last change on this file since 14999 was 14999, checked in by davidb, 13 years ago

Password limit was restricted to max 8 chars. This has been increased to 128.

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