• Main Page
  • Classes
  • Files
  • File List
  • File Members

Beesnest/DcLoginManager.cpp

Go to the documentation of this file.
00001 /*
00002 Copyright 2007 Erez Bibi (erezbibi@users.sourceforge.net)
00003 This file is part of Beesnest.
00004 
00005 Beesnest is free software; you can redistribute it and/or modify
00006 it under the terms of the GNU General Public License as published by
00007 the Free Software Foundation; either version 2 of the License, or
00008 (at your option) any later version.
00009 
00010 Beesnest is distributed in the hope that it will be useful,
00011 but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 GNU General Public License for more details.
00014 
00015 You should have received a copy of the GNU General Public License
00016 along with Beesnest; if not, write to the Free Software
00017 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00018 */
00019 
00020 
00021 /**
00022  * DcLoginManager.cpp: implementation of the CdcLoginManager class.
00023  * for documentation see the header file.
00024  */
00025 
00026 #include "DcLoginManager.h"
00027 
00028 
00029 /* Static variables. */
00030 CdcLoginManager::t_users_vector CdcLoginManager::vUsers;
00031 CdcLoginManager::t_challenges_map CdcLoginManager::mChallenges;
00032 CdcMutex CdcLoginManager::oMapMutex;
00033 CdcString CdcLoginManager::sLoginFileName;
00034 CdcString CdcLoginManager::sDenyFileName;
00035 bool CdcLoginManager::bBuiltInLogin;
00036 bool CdcLoginManager::bAutoCookie;
00037 int CdcLoginManager::nStaleNonce;
00038 int CdcLoginManager::nExpireNonce;
00039 int CdcLoginManager::nOldCodeValid;
00040 CdcThread CdcLoginManager::oCleanThread;
00041 CdcSemaphore CdcLoginManager::oStartThread;
00042 bool CdcLoginManager::isCleanThreadGo = true; /* In fact I never turn it to False
00043                                                     But i still have the option... */
00044 int volatile CdcLoginManager::nSomeChalTaken = 0;
00045 
00046 CdcLoginManager::CdcUser::CdcUser (ifstream& inf)
00047 : nCode (EMPTY_STR), isLogedin (false)
00048 {
00049     oMutex.Lock ();
00050 
00051     char temp [BUFF_LEN];
00052     inf.getline (temp, BUFF_LEN, '~');
00053     sUsername = CdcString (temp);
00054     inf.getline (temp, BUFF_LEN, '~');
00055 
00056     for (int i = 0; i < strlen (temp); ++i)
00057         temp[i] -= 127;
00058     sPassword = CdcString (temp);
00059     oMutex.Unlock ();
00060 }
00061 
00062 bool CdcLoginManager::CdcUser::WriteToFile (ofstream& outf)
00063 {   /* Solve some bug I have.... */
00064     if (sUsername.IsEmpty ()) return false;
00065 
00066     oMutex.Lock ();
00067     outf.write (sUsername.GetBuffer (), sUsername.GetLength ());
00068     outf.put ('~');
00069 
00070     const char* ptemp = sPassword.GetBuffer ();
00071     for (int i = 0; i < strlen (ptemp); ++i)
00072         outf.put ((char)(ptemp[i] + 127));
00073     outf.put ('~');
00074     oMutex.Unlock ();
00075     return true;
00076 }
00077 
00078 
00079 void CdcLoginManager::Load (const char* file_name)
00080 {
00081     sLoginFileName = CdcParameters::GetParam (
00082         "Login-Manager->Login-Page");
00083     sDenyFileName = CdcParameters::GetParam (
00084         "Login-Manager->Denied-Page");
00085     bAutoCookie = CdcParameters::GetParam (
00086         "Login-Manager->Auto-Cookie") == "yes";
00087     bBuiltInLogin = sLoginFileName.IsEmpty () == false;
00088     nStaleNonce = CdcParameters::ToInt (CdcParameters::GetParam (
00089         "Login-Manager->Stale"));
00090     nExpireNonce = CdcParameters::ToInt (CdcParameters::GetParam (
00091         "Login-Manager->Expire"));
00092     nOldCodeValid = CdcParameters::ToInt (CdcParameters::GetParam (
00093         "Login-Manager->Old-Code-Life"));
00094 
00095     ifstream inf (file_name);
00096     if (inf.is_open () == 0) return;
00097 
00098     while (inf.eof () == 0)
00099     {
00100         CdcUser* user = new CdcUser (inf);
00101         if (user == NULL)
00102             throw CdcException ("Out of memory.");
00103         vUsers.push_back (user);
00104     }
00105     inf.close ();
00106 
00107     /* Create the cleaning thread. */
00108     oCleanThread.MakeThread (DcClean, &isCleanThreadGo);
00109     oCleanThread.SetPriority (DC_TP_LOW);
00110 }
00111 
00112 
00113 void CdcLoginManager::Save (const char* file_name)
00114 {
00115     ofstream outf (file_name);
00116     if (outf.is_open () == false)
00117         throw CdcExceptionFile ("Cannot open users database file.");
00118 
00119     t_users_vector::iterator itr;
00120     for (itr = vUsers.begin(); itr != vUsers.end(); itr++)
00121         (*itr)->WriteToFile (outf);
00122     outf.close ();
00123 }
00124 
00125 
00126 void CdcLoginManager::LoginUser (int id, CdcRequest& req, CdcResponse& res)
00127 {
00128     vUsers[id]->GetStatusObject ()->Login ();
00129     vUsers[id]->Logedin (true);
00130     vUsers[id]->SetIP (req.GetVariable ("BN_ClentIP"));
00131 }
00132 
00133 void CdcLoginManager::SetRandCode (int id, CdcRequest& req, CdcResponse& res)
00134 {
00135     vUsers[id]->SetCode (CdcCrypto::GetRandomString ());
00136     if (bAutoCookie)
00137     {
00138         res.SetCookie (DC_USER_ID, CdcString::IntToString (id));
00139         res.SetCookie (DC_USER_CODE, vUsers[id]->GetCode ());
00140     }
00141     else if (bBuiltInLogin)
00142     {
00143         req.SetVariable (DC_USER_ID, CdcString::IntToString (id));
00144         req.SetVariable (DC_USER_CODE, vUsers[id]->GetCode ());
00145     }
00146 }
00147 
00148 void CdcLoginManager::Clean ()
00149 {
00150     /* Erase all wrong, stale or old challenges. */
00151     if (nSomeChalTaken > 0) return;             /* Can't clean now. */
00152     CdcHttpTime now = CdcHttpTime::GetCurrentTime ();
00153     oMapMutex.Lock ();
00154     t_challenges_map::iterator itr = mChallenges.begin();
00155     while (itr != mChallenges.end())
00156     {
00157         if (nSomeChalTaken > 0) break;          /* Have to stop cleaning. */
00158         t_challenges_map::iterator prev = itr++;
00159         if (prev->second->isCanceld == true ||
00160             prev->second->isForDelete == true ||
00161             now - prev->second->oBuildTime > nExpireNonce)
00162         {
00163             delete prev->second;
00164             mChallenges.erase (prev);
00165         }
00166     }
00167     oMapMutex.Unlock ();
00168 }
00169 
00170 bool CdcLoginManager::CheckLogout (CdcRequest& req, CdcResponse& res)
00171 {
00172     if (req.GetVariable (DC_LOGOUT) == "yes")
00173     {
00174         LogoutUser (req, res);
00175         return true;
00176     }
00177     return false;
00178 }
00179 
00180 void CdcLoginManager::LogoutUser (CdcRequest& req, CdcResponse& res)
00181 {
00182     /* Find the User ID. */
00183     CdcDaaResponse temp;
00184     bool isRes = false, isVar = false;
00185     int id = GetUserID (req, temp, isRes, isVar);
00186     if (id < 0) return; /* Could not find the user id. */
00187     /* Clean user object. */
00188     vUsers[id]->GetStatusObject ()->Logout ();
00189     vUsers[id]->Logedin (false);
00190     vUsers[id]->SetCode (EMPTY_STR);
00191     if (bBuiltInLogin)
00192     {   /* Erase built-in nonce if exist and login vars. */
00193         if (isRes) req.SetVariable (DC_NONCE, EMPTY_STR);
00194         req.SetVariable (DC_USER_ID, EMPTY_STR);
00195         req.SetVariable (DC_USER_CODE, EMPTY_STR);
00196     }
00197     else if (isRes) /* Erase DAA header if any, and match challenge. */
00198         req.EraseHeaderField ("Authorization");
00199     if (isRes)
00200     {
00201         oMapMutex.Lock ();
00202         t_challenges_map::iterator itr = mChallenges.find (temp.sNonce);
00203         if (itr != mChallenges.end ())
00204         {
00205             itr->second->isCanceld = true;
00206             itr->second->isForDelete = true;
00207             oStartThread.Call ();
00208         }
00209         oMapMutex.Unlock ();
00210     }
00211     if (bAutoCookie)
00212     {   /* Erase Cookies if needed. */
00213         res.SetCookie (DC_USER_ID, "-1");
00214         res.SetCookie (DC_USER_CODE, EMPTY_STR, -1);
00215     }
00216 }
00217 
00218 
00219 bool CdcLoginManager::CheckCode (int id, CdcRequest& req, CdcResponse& res)
00220 {
00221     CdcString code = req.GetVariable (DC_USER_CODE);
00222 
00223     if (code.IsEmpty () == false &&
00224         req.GetVariable ("BN_ClentIP") == vUsers[id]->GetIP ())
00225     {
00226         if (vUsers[id]->GetCode () == code)
00227         {
00228             SetRandCode (id, req, res);
00229             return true;
00230         }
00231         if (nOldCodeValid > 0 &&
00232             vUsers[id]->GetLastCode () == code &&
00233             CdcHttpTime::GetCurrentTime () - vUsers[id]->GetLastLoginTime ()
00234             < nOldCodeValid)
00235             return true;    /* That is multi-thread request from the same page. */
00236                             /* Or most likely it is... */
00237     }
00238     return false;
00239 }
00240 
00241 
00242 /*
00243 1. The MD5 hash of the combined user name, authentication realm and password
00244     is calculated. The result is referred to as HA1.
00245 2. The MD5 hash of the combined method and digest URI is calculated, e.g. of
00246     "GET" and "/dir/index.html". The result is referred to as HA2.
00247 3. The MD5 hash of the combined HA1 result, server nonce (nonce), request
00248     counter (nc), client nonce (cnonce), quality of protection code (qop) and
00249     HA2 result is calculated. The result is the "response" value provided by
00250     the client.
00251 4. connect two string with ':'.
00252 */
00253 bool CdcLoginManager::CheckResponse (int id, const CdcDaaResponse& DaaRes,
00254     bool& stale, bool& wrong)
00255 {   /* Get the right challenge from the map. */
00256     ++nSomeChalTaken;   /* Signal cleaning thread not to work. */
00257     oMapMutex.Lock ();  /* Wait on Mutex but release immediately. */
00258     oMapMutex.Unlock ();
00259     t_challenges_map::iterator itr = mChallenges.find (DaaRes.sNonce);
00260     if (itr == mChallenges.end () || itr->second->isCanceld == true)
00261     {   /* Didn't found a matched challenge. */
00262         --nSomeChalTaken;
00263         return false;
00264     }
00265     CdcDaaChallenge* pchal = itr->second;
00266     int nc = strtoul(DaaRes.sNC.GetBuffer (), NULL, 16);
00267     /* Check the response - first. */
00268     bool fail = pchal->sRealm != DaaRes.sRealm ||
00269                 pchal->sOpaque != DaaRes.sOpaque;
00270     /* Check the response - second. */
00271     if (fail == false)
00272     {   /* Build the response hash on the server side. */
00273         CdcString HA1 = CdcCrypto::MakeHash (vUsers[id]->GetUsername () + ':'
00274             + DaaRes.sRealm + ':' + vUsers[id]->GetPassword ());
00275         CdcString HA2 = CdcCrypto::MakeHash (DaaRes.sMethod + ':' + DaaRes.sURL);
00276         CdcString ResHash = CdcCrypto::MakeHash (HA1 + ':' + DaaRes.sNonce +
00277             ':' + DaaRes.sNC + ':' + DaaRes.sCnonce + ':' + DaaRes.sQOP + ':'
00278             + HA2);
00279         fail = wrong = !(ResHash == DaaRes.sResponse);
00280     }
00281     if (fail == false)
00282     {   /* Check for stale or old and NC. */
00283         int time_diff = CdcHttpTime::GetCurrentTime () - pchal->oBuildTime;
00284         fail = time_diff > nStaleNonce;
00285         stale = (time_diff > nStaleNonce && time_diff < nExpireNonce) ||
00286                 (pchal->nNC > nc);  /* wrong counter marks as stale because */
00287     }                               /* sometimes browser send wrong counter. */
00288     if (fail == true)
00289     {
00290         /* Response is wrong or challenge is stale. */
00291         pchal->isForDelete = true;
00292         --nSomeChalTaken;       /* Don't use this challenge any more. */
00293         oStartThread.Call ();   /* Signal to the cleaning thread. */
00294         return false;
00295     }
00296     /* Response is good. */
00297     pchal->nNC = nc + 1;    /* Increase counter */
00298     --nSomeChalTaken;       /* Don't use this challenge any more. */
00299     return true;
00300 }
00301 
00302 
00303 bool CdcLoginManager::CheckGroupAccess (int id, const CdcString& group)
00304 {
00305     CdcString group_name = CdcParameters::GetFullName (group,
00306         "Login-Manager->Groups");
00307     return CdcParameters::IsUnder (group_name,
00308         vUsers[id]->GetUsername (), true);
00309 }
00310 
00311 
00312 void CdcLoginManager::BuildLoginPage (CdcRequest& req, CdcResponse& res,
00313                                       bool stale)
00314 {   /* Create new challenge object. */
00315     CdcDaaChallenge* pchal = new CdcDaaChallenge ();
00316     if (pchal == NULL) throw CdcException ("Out of memory.");
00317     /* Fill in challenge. */
00318     pchal->sNonce = CdcCrypto::GetRandomString (35);
00319     pchal->sOpaque = CdcCrypto::GetRandomString ();
00320     pchal->sRealm = req.GetAliasHost ();
00321     pchal->isForDelete = false;
00322     pchal->nNC = 1;
00323     /*  And insert to map. No need to lock list. */
00324     mChallenges[pchal->sNonce] = pchal;
00325     /* Put challenge in header or variables. */
00326     if (bBuiltInLogin)
00327     {   /* Set Request variables for built in method. */
00328         req.SetVariable (DC_NONCE, pchal->sNonce);
00329         req.SetVariable (DC_OPAQUE, pchal->sOpaque);
00330         req.SetVariable (DC_REALM, pchal->sRealm);
00331     }
00332     else
00333     {   /* Set response header for DAA. */
00334         CdcString DAA = "Digest qop=\"auth\",";
00335         DAA = DAA + "realm=\"" + pchal->sRealm + "\",";
00336         DAA = DAA + "nonce=\"" + pchal->sNonce + "\",";
00337         if (stale)
00338             DAA = DAA + "stale=\"true\",";
00339         DAA = DAA + "opaque=\"" + pchal->sOpaque + "\"";
00340         res.SetHeaderField ("WWW-Authenticate", DAA);
00341         res.SetStatus (401);
00342     }
00343 }
00344 
00345 
00346 CdcUserStatus* CdcLoginManager::CheckAccess (const CdcString& group,
00347                                                CdcRequest& req,
00348                                                CdcResponse& res,
00349                                                int& ret_code,
00350                                                CdcString& new_page)
00351 {   /* Get user ID and Response for challenge (if exist). */
00352     CdcDaaResponse DaaRes;
00353     bool isRes = false, isVar = false, stale = false, wrong = false, access = false;
00354     int id = GetUserID (req, DaaRes, isRes, isVar);
00355     /* Check permission in one of two ways. */
00356     if (id >= 0)
00357     {   /*  Built In first. */
00358         access = CheckCode (id, req, res);
00359         if (access == false && isRes && DaaRes.sURL == req.GetFullURL ())
00360         {   /* Or DAA (I try it anyway if the user code was wrong). */
00361             if ((access = CheckResponse (id, DaaRes, stale, wrong)) == true)
00362             {   /* Set random code also if user is already logged in. */
00363                 SetRandCode (id, req, res);
00364                 if (vUsers[id]->IsLogedin () == false)
00365                     LoginUser (id, req, res);   /* Login user if this is the first time. */
00366             }
00367             if (access == false && stale == false)
00368                 PrintString ("Login Faild - " + req.GetVariable ("BN_ClentIP"), PS_SEC);
00369         }
00370     }
00371     /* If permission denied - send login page. */
00372     if (access == false)
00373     {
00374         BuildLoginPage (req, res, stale);
00375         ret_code = DC_LM_SEND;
00376         new_page = sLoginFileName;  /* May be empty if using DAA. */
00377         if (bBuiltInLogin)
00378         {
00379             if (id < 0) req.SetVariable (DC_LOGIN_MSG, "Please log in");
00380             else if (wrong) req.SetVariable (DC_LOGIN_MSG, "Username or Password is wrong!");
00381             else req.SetVariable (DC_LOGIN_MSG, "You have to log in again");
00382         }
00383         return NULL;
00384     }
00385     req.SetVariable (DC_USER_NAME, vUsers[id]->GetUsername ());
00386 
00387     if (group.IsEmpty ())
00388     {
00389         ret_code = DC_LM_NONE;  /* if group is empty - ignore this check. */
00390         return vUsers[id]->GetStatusObject ();  /* But return user status object. */
00391     }
00392     /* If no permission for this group - send deny page. */
00393     if (CheckGroupAccess (id, group) == false)
00394     {
00395         ret_code = DC_LM_SEND;
00396         new_page = sDenyFileName;
00397         return NULL;
00398     }
00399     /* return the user status object. */
00400     ret_code = DC_LM_OK;
00401     return vUsers[id]->GetStatusObject ();
00402 }
00403 
00404 int CdcLoginManager::GetUserID (const CdcRequest& req, CdcDaaResponse& DaaRes,
00405                                 bool& isRes, bool& isVar)
00406 {
00407     /* Try response Header/Vars first. */
00408     if (bBuiltInLogin)
00409         isRes = BuildResFromVar (req, DaaRes);
00410     else
00411         isRes = BuildResFromHeader (req, DaaRes);
00412     if (isRes == true)
00413         return FindUser (DaaRes.sUsername);
00414     /* If not try UserID Var. */
00415     int id = -1;
00416     CdcString user_id = req.GetVariable (DC_USER_ID);
00417     isVar = !user_id.IsEmpty ();
00418     if (isVar)
00419         id = CdcParameters::ToInt (user_id);
00420     return id > vUsers.size () ? -1 : id;
00421 }
00422 
00423 bool CdcLoginManager::BuildResFromVar (const CdcRequest& req, CdcDaaResponse& DaaRes)
00424 {
00425     DaaRes.sNonce = req.GetVariable (DC_NONCE);
00426     if (DaaRes.sNonce.IsEmpty ()) return false; /* If no nonce - probably no response. */
00427     DaaRes.sUsername = req.GetVariable (DC_USER_NAME);
00428     DaaRes.sRealm = req.GetVariable (DC_REALM);
00429     DaaRes.sOpaque = req.GetVariable (DC_OPAQUE);
00430     DaaRes.sURL = req.GetVariable (DC_URL);
00431     DaaRes.sMethod = req.GetMethod ();
00432     DaaRes.sQOP = "auth";       /* I'm not using this option. */
00433     DaaRes.sCnonce = req.GetVariable (DC_CNONCE);
00434     DaaRes.sResponse = req.GetVariable (DC_RESPONSE);
00435     DaaRes.sNC = req.GetVariable (DC_NC);
00436     return true;
00437 }
00438 
00439 bool CdcLoginManager::BuildResFromHeader (const CdcRequest& req, CdcDaaResponse& DaaRes)
00440 {
00441     CdcString Auth = req.GetHeaderField ("Authorization");
00442     if (Auth.IsEmpty ()) return false;
00443     DaaRes.sUsername = GetProp ("username", Auth);
00444     DaaRes.sRealm = GetProp ("realm", Auth);
00445     DaaRes.sNonce = GetProp ("nonce", Auth);
00446     DaaRes.sOpaque = GetProp ("opaque", Auth);
00447     DaaRes.sURL = GetProp ("uri", Auth);
00448     DaaRes.sMethod = req.GetMethod ();
00449     DaaRes.sQOP = GetProp ("qop", Auth);
00450     DaaRes.sCnonce = GetProp ("cnonce", Auth);
00451     DaaRes.sResponse = GetProp ("response", Auth);
00452     DaaRes.sNC = GetProp ("nc", Auth);
00453     return true;
00454 }
00455 
00456 CdcString CdcLoginManager::GetProp (const CdcString& name, const CdcString& tag)
00457 {
00458     int place = tag.Find (name + '=');
00459     /* if no property - return empty string. */
00460     if (place < 0) return EMPTY_STR;
00461     CdcString ret = tag.Mid (place + name.GetLength () + 1);
00462     ret.TrimLeft ();
00463     ret = ret.Left (ret.FindOneOf (",\n\r"));
00464     ret.TrimRight ();
00465     if (ret[0] == '\"' && ret[ret.GetLength () - 1] == '\"')
00466         ret = ret.Mid (1, ret.GetLength () - 2);
00467     return ret;
00468 }
00469 
00470 int CdcLoginManager::FindUser (const CdcString& username)
00471 {
00472     int id;
00473     if (username.IsEmpty ()) return -1;
00474     t_users_vector::iterator itr;
00475     for (itr = vUsers.begin(), id = 0; itr != vUsers.end(); itr++, id++)
00476     {
00477         if ((*itr)->GetUsername () == username)
00478             break;
00479     }
00480     if (itr == vUsers.end()) return -1;
00481     return id;
00482 }
00483 
00484 
00485 bool CdcLoginManager::AddUser (const CdcString& username,
00486                                const CdcString& password)
00487 {
00488     int id = FindUser (username);
00489     if (id >= 0) return false;
00490 
00491     CdcUser* user = new CdcUser (username, password);
00492     if (user == NULL)
00493         throw CdcException ("Out of memory.");
00494 
00495     vUsers.push_back (user);
00496 
00497     return true;
00498 }
00499 
00500 
00501 bool CdcLoginManager::ChangePassword (const CdcString& username,
00502                                       const CdcString& old_password,
00503                                       const CdcString new_password)
00504 {
00505     int id = FindUser (username);
00506     if (id < 0) return false;
00507 
00508     if (vUsers[id]->GetPassword () != old_password)
00509         return false;
00510 
00511     vUsers[id]->SetPassword (new_password);
00512     return true;
00513 }
00514 
00515 
00516 bool CdcLoginManager::RemoveUser (const CdcString& username,
00517                                   const CdcString& password)
00518 {
00519     int id = FindUser (username);
00520     if (id < 0) return false;
00521 
00522     if (vUsers[id]->GetPassword () != password)
00523         return false;
00524 
00525     CdcUser* ptemp = vUsers[id];
00526     vUsers.erase(vUsers.begin() + id);
00527     delete ptemp;
00528 
00529     return true;
00530 }
00531 
00532 
00533 int DcClean (void* pParam)
00534 {
00535     bool* pRun = (bool*)pParam;
00536 
00537     while (*pRun == true)
00538     {   /* Wait for event, and clean the challenge map. */
00539         CdcLoginManager::oStartThread.Wait (600000);    /* Go every 10 min any way. */
00540         if (*pRun == false) return 0;       /* Close this thread. */
00541         Sleep (DC_CHL_STL_ADD * 1000);      /* Wait a little more if other frames
00542                                                 will use this challenge. */
00543         if (*pRun == false) return 0;
00544         CdcLoginManager::Clean ();
00545     }
00546     return 0;
00547 }

Generated on Mon Oct 11 2010 16:23:24 for Beesnest by  doxygen 1.7.2