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

Last change on this file since 13844 was 13844, checked in by mdewsnip, 14 years ago

Jeffrey's (DL Consulting) changes to userdb to make it a proper class, so it can be easily extended for collection-specific receptionists.

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