root/gsdl/trunk/src/recpt/userdb.cpp @ 14999

Revision 14999, 16.2 KB (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
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 browser.