123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <kopano/platform.h>
- #include <chrono>
- #include <memory>
- #include <mutex>
- #include <new>
- #include <utility>
- #include <cerrno>
- #include <cstring>
- #include <dirent.h>
- #include <poll.h>
- #include <pwd.h>
- #include <sys/stat.h>
- #include <mapidefs.h>
- #include <mapitags.h>
- #include <kopano/lockhelper.hpp>
- #include <kopano/UnixUtil.h>
- #include "ECSession.h"
- #include "ECSessionManager.h"
- #include "ECUserManagement.h"
- #include "ECSecurity.h"
- #include "ECPluginFactory.h"
- #include "SSLUtil.h"
- #include <kopano/stringutil.h>
- #include "ECDatabase.h"
- #include "ECDatabaseUtils.h" // used for PR_INSTANCE_KEY
- #include "SOAPUtils.h"
- #include "ics.h"
- #include "ECICS.h"
- #include <kopano/ECIConv.h>
- #include "versions.h"
- #if defined LINUX || !defined UNICODE
- #define WHITESPACE " \t\n\r"
- #else
- #define WHITESPACE L" \t\n\r"
- #endif
- namespace KC {
- // possible missing SSL function
- #ifndef HAVE_EVP_PKEY_CMP
- static int EVP_PKEY_cmp(EVP_PKEY *a, EVP_PKEY *b)
- {
- if (a->type != b->type)
- return -1;
- if (EVP_PKEY_cmp_parameters(a, b) == 0)
- return 0;
- switch (a->type)
- {
- case EVP_PKEY_RSA:
- if (BN_cmp(b->pkey.rsa->n,a->pkey.rsa->n) != 0
- || BN_cmp(b->pkey.rsa->e,a->pkey.rsa->e) != 0)
- return 0;
- break;
- case EVP_PKEY_DSA:
- if (BN_cmp(b->pkey.dsa->pub_key,a->pkey.dsa->pub_key) != 0)
- return 0;
- break;
- case EVP_PKEY_DH:
- return -2;
- default:
- return -2;
- }
- return 1;
- }
- #endif
- void CreateSessionID(unsigned int ulCapabilities, ECSESSIONID *lpSessionId)
- {
- ssl_random(!!(ulCapabilities & KOPANO_CAP_LARGE_SESSIONID), lpSessionId);
- }
- /*
- BaseType session
- */
- BTSession::BTSession(const char *src_addr, ECSESSIONID sessionID,
- ECDatabaseFactory *lpDatabaseFactory, ECSessionManager *lpSessionManager,
- unsigned int ulCapabilities) :
- m_strSourceAddr(src_addr), m_sessionID(sessionID),
- m_lpDatabaseFactory(lpDatabaseFactory),
- m_lpSessionManager(lpSessionManager),
- m_ulClientCapabilities(ulCapabilities)
- {
- m_ulRefCount = 0;
- m_sessionTime = GetProcessTime();
- m_ulSessionTimeout = 300;
- m_bCheckIP = true;
- m_lpUserManagement = NULL;
- m_ulRequests = 0;
- m_ulLastRequestPort = 0;
- }
- void BTSession::SetClientMeta(const char *const lpstrClientVersion, const char *const lpstrClientMisc)
- {
- m_strClientApplicationVersion = lpstrClientVersion ? lpstrClientVersion : "";
- m_strClientApplicationMisc = lpstrClientMisc ? lpstrClientMisc : "";
- }
- void BTSession::GetClientApplicationVersion(std::string *lpstrClientApplicationVersion)
- {
- lpstrClientApplicationVersion->assign(m_strClientApplicationVersion);
- }
- void BTSession::GetClientApplicationMisc(std::string *lpstrClientApplicationMisc)
- {
- scoped_lock lock(m_hRequestStats);
- lpstrClientApplicationMisc->assign(m_strClientApplicationMisc);
- }
- ECRESULT BTSession::Shutdown(unsigned int ulTimeout) {
- return erSuccess;
- }
- ECRESULT BTSession::ValidateOriginator(struct soap *soap)
- {
- if (!m_bCheckIP)
- return erSuccess;
- const char *s = ::GetSourceAddr(soap);
- if (strcmp(m_strSourceAddr.c_str(), s) == 0)
- return erSuccess;
- ec_log_err("Denying access to session from source \"%s\" due to unmatched establishing source \"%s\"",
- s, m_strSourceAddr.c_str());
- return KCERR_END_OF_SESSION;
- }
- void BTSession::UpdateSessionTime()
- {
- m_sessionTime = GetProcessTime();
- }
- ECRESULT BTSession::GetDatabase(ECDatabase **lppDatabase)
- {
- return GetThreadLocalDatabase(this->m_lpDatabaseFactory, lppDatabase);
- }
- ECRESULT BTSession::GetAdditionalDatabase(ECDatabase **lppDatabase)
- {
- std::string str;
- return this->m_lpDatabaseFactory->CreateDatabaseObject(lppDatabase, str);
- }
- ECRESULT BTSession::GetServerGUID(GUID* lpServerGuid){
- return m_lpSessionManager->GetServerGUID(lpServerGuid);
- }
- ECRESULT BTSession::GetNewSourceKey(SOURCEKEY* lpSourceKey){
- return m_lpSessionManager->GetNewSourceKey(lpSourceKey);
- }
- void BTSession::Lock()
- {
- // Increase our refcount by one
- scoped_lock lock(m_hThreadReleasedMutex);
- ++this->m_ulRefCount;
- }
- void BTSession::Unlock()
- {
- // Decrease our refcount by one, signal ThreadReleased if RefCount == 0
- scoped_lock lock(m_hThreadReleasedMutex);
- --this->m_ulRefCount;
- if(!IsLocked())
- m_hThreadReleased.notify_one();
- }
- time_t BTSession::GetIdleTime()
- {
- return GetProcessTime() - m_sessionTime;
- }
- void BTSession::RecordRequest(struct soap* soap)
- {
- scoped_lock lock(m_hRequestStats);
- m_strLastRequestURL = soap->endpoint;
- m_ulLastRequestPort = soap->port;
- if (soap->proxy_from != nullptr && soap_info(soap)->bProxy)
- m_strProxyHost = soap->host;
- ++m_ulRequests;
- }
- unsigned int BTSession::GetRequests()
- {
- scoped_lock lock(m_hRequestStats);
- return m_ulRequests;
- }
- void BTSession::GetRequestURL(std::string *lpstrClientURL)
- {
- scoped_lock lock(m_hRequestStats);
- lpstrClientURL->assign(m_strLastRequestURL);
- }
- void BTSession::GetProxyHost(std::string *lpstrProxyHost)
- {
- scoped_lock lock(m_hRequestStats);
- lpstrProxyHost->assign(m_strProxyHost);
- }
- void BTSession::GetClientPort(unsigned int *lpulPort)
- {
- scoped_lock lock(m_hRequestStats);
- *lpulPort = m_ulLastRequestPort;
- }
- size_t BTSession::GetInternalObjectSize()
- {
- scoped_lock lock(m_hRequestStats);
- return MEMORY_USAGE_STRING(m_strSourceAddr) +
- MEMORY_USAGE_STRING(m_strLastRequestURL) +
- MEMORY_USAGE_STRING(m_strProxyHost);
- }
- ECSession::ECSession(const char *src_addr, ECSESSIONID sessionID,
- ECSESSIONGROUPID ecSessionGroupId, ECDatabaseFactory *lpDatabaseFactory,
- ECSessionManager *lpSessionManager, unsigned int ulCapabilities,
- AUTHMETHOD ulAuthMethod, int pid,
- const std::string &cl_ver, const std::string &cl_app,
- const std::string &cl_app_ver, const std::string &cl_app_misc) :
- BTSession(src_addr, sessionID, lpDatabaseFactory, lpSessionManager,
- ulCapabilities),
- m_ulAuthMethod(ulAuthMethod), m_ulConnectingPid(pid),
- m_ecSessionGroupId(ecSessionGroupId), m_strClientVersion(cl_ver),
- m_ulClientVersion(KOPANO_VERSION_UNKNOWN), m_strClientApp(cl_app)
- {
- m_lpTableManager = new ECTableManager(this);
- m_strClientApplicationVersion = cl_app_ver;
- m_strClientApplicationMisc = cl_app_misc;
- ParseKopanoVersion(cl_ver, &m_ulClientVersion);
- // Ignore result.
- m_ulSessionTimeout = atoi(lpSessionManager->GetConfig()->GetSetting("session_timeout"));
- if (m_ulSessionTimeout < 300)
- m_ulSessionTimeout = 300;
- m_bCheckIP = strcmp(lpSessionManager->GetConfig()->GetSetting("session_ip_check"), "no") != 0;
- // Offline implements its own versions of these objects
- m_lpUserManagement = new ECUserManagement(this, m_lpSessionManager->GetPluginFactory(), m_lpSessionManager->GetConfig());
- m_lpEcSecurity = new ECSecurity(this, m_lpSessionManager->GetConfig(), m_lpSessionManager->GetAudit());
- // Atomically get and AddSession() on the sessiongroup. Needs a ReleaseSession() on the session group to clean up.
- m_lpSessionManager->GetSessionGroup(ecSessionGroupId, this, &m_lpSessionGroup);
- }
- ECSession::~ECSession()
- {
- Shutdown(0);
- /*
- * Release our reference to the session group; none of the threads of this session are
- * using the object since there are now 0 threads on this session (except this thread)
- * Afterwards tell the session manager that the sessiongroup may be an orphan now.
- */
- if (m_lpSessionGroup) {
- m_lpSessionGroup->ReleaseSession(this);
- m_lpSessionManager->DeleteIfOrphaned(m_lpSessionGroup);
- }
- delete m_lpTableManager;
- delete m_lpUserManagement;
- delete m_lpEcSecurity;
- }
- /**
- * Shut down the session:
- *
- * - Signal sessiongroup that long-running requests should be cancelled
- * - Wait for all users of the session to exit
- *
- * If the wait takes longer than ulTimeout milliseconds, KCERR_TIMEOUT is
- * returned. If this is the case, it is *not* safe to delete the session
- *
- * @param ulTimeout Timeout in milliseconds
- * @result erSuccess or KCERR_TIMEOUT
- */
- ECRESULT ECSession::Shutdown(unsigned int ulTimeout)
- {
- ECRESULT er = erSuccess;
- /* Shutdown blocking calls for this session on our session group */
- if (m_lpSessionGroup != nullptr)
- m_lpSessionGroup->ShutdownSession(this);
- /* Wait until there are no more running threads using this session */
- std::unique_lock<std::mutex> lk(m_hThreadReleasedMutex);
- while(IsLocked())
- if (m_hThreadReleased.wait_for(lk, std::chrono::milliseconds(ulTimeout)) == std::cv_status::timeout)
- break;
- lk.unlock();
- if (IsLocked())
- er = KCERR_TIMEOUT;
- return er;
- }
- ECRESULT ECSession::AddAdvise(unsigned int ulConnection, unsigned int ulKey, unsigned int ulEventMask)
- {
- ECRESULT hr = erSuccess;
- Lock();
- if (m_lpSessionGroup)
- hr = m_lpSessionGroup->AddAdvise(m_sessionID, ulConnection, ulKey, ulEventMask);
- else
- hr = KCERR_NOT_INITIALIZED;
- Unlock();
- return hr;
- }
- ECRESULT ECSession::AddChangeAdvise(unsigned int ulConnection, notifySyncState *lpSyncState)
- {
- ECRESULT er = erSuccess;
- string strQuery;
- ECDatabase* lpDatabase = NULL;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow;
- ULONG ulChangeId = 0;
- Lock();
- if (!m_lpSessionGroup) {
- er = KCERR_NOT_INITIALIZED;
- goto exit;
- }
- er = m_lpSessionGroup->AddChangeAdvise(m_sessionID, ulConnection, lpSyncState);
- if (er != hrSuccess)
- goto exit;
- er = GetDatabase(&lpDatabase);
- if (er != erSuccess)
- goto exit;
- strQuery = "SELECT c.id FROM changes AS c JOIN syncs AS s "
- "ON s.sourcekey=c.parentsourcekey "
- "WHERE s.id=" + stringify(lpSyncState->ulSyncId) + " "
- "AND c.id>" + stringify(lpSyncState->ulChangeId) + " "
- "AND c.sourcesync!=" + stringify(lpSyncState->ulSyncId) + " "
- "AND c.change_type >= " + stringify(ICS_MESSAGE) + " "
- "AND c.change_type & " + stringify(ICS_MESSAGE) + " != 0 "
- "ORDER BY c.id DESC "
- "LIMIT 1";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if (er != hrSuccess)
- goto exit;
- if (lpDatabase->GetNumRows(lpDBResult) == 0)
- goto exit;
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if (lpDBRow == NULL || lpDBRow[0] == NULL) {
- er = KCERR_DATABASE_ERROR;
- ec_log_err("ECSession::AddChangeAdvise(): row or column null");
- goto exit;
- }
- ulChangeId = strtoul(lpDBRow[0], NULL, 0);
- er = m_lpSessionGroup->AddChangeNotification(m_sessionID, ulConnection, lpSyncState->ulSyncId, ulChangeId);
- exit:
- Unlock();
- return er;
- }
- ECRESULT ECSession::DelAdvise(unsigned int ulConnection)
- {
- ECRESULT hr = erSuccess;
- Lock();
- if (m_lpSessionGroup)
- hr = m_lpSessionGroup->DelAdvise(m_sessionID, ulConnection);
- else
- hr = KCERR_NOT_INITIALIZED;
- Unlock();
- return hr;
- }
- ECRESULT ECSession::AddNotificationTable(unsigned int ulType, unsigned int ulObjType, unsigned int ulTableId, sObjectTableKey* lpsChildRow, sObjectTableKey* lpsPrevRow, struct propValArray *lpRow)
- {
- ECRESULT hr = hrSuccess;
- Lock();
- if (m_lpSessionGroup)
- hr = m_lpSessionGroup->AddNotificationTable(m_sessionID, ulType, ulObjType, ulTableId, lpsChildRow, lpsPrevRow, lpRow);
- else
- hr = KCERR_NOT_INITIALIZED;
- Unlock();
- return hr;
- }
- ECRESULT ECSession::GetNotifyItems(struct soap *soap, struct notifyResponse *notifications)
- {
- ECRESULT hr = erSuccess;
- Lock();
- if (m_lpSessionGroup)
- hr = m_lpSessionGroup->GetNotifyItems(soap, m_sessionID, notifications);
- else
- hr = KCERR_NOT_INITIALIZED;
- Unlock();
- return hr;
- }
- void ECSession::AddBusyState(pthread_t threadId, const char *lpszState,
- const struct timespec &threadstart, double start)
- {
- if (!lpszState) {
- ec_log_err("Invalid argument \"lpszState\" in call to ECSession::AddBusyState()");
- return;
- }
- scoped_lock lock(m_hStateLock);
- m_mapBusyStates[threadId].fname = lpszState;
- m_mapBusyStates[threadId].threadstart = threadstart;
- m_mapBusyStates[threadId].start = start;
- m_mapBusyStates[threadId].threadid = threadId;
- m_mapBusyStates[threadId].state = SESSION_STATE_PROCESSING;
- }
- void ECSession::UpdateBusyState(pthread_t threadId, int state)
- {
- scoped_lock lock(m_hStateLock);
- auto i = m_mapBusyStates.find(threadId);
- if (i != m_mapBusyStates.cend())
- i->second.state = state;
- else
- assert(false);
- }
- void ECSession::RemoveBusyState(pthread_t threadId)
- {
- scoped_lock lock(m_hStateLock);
- auto i = m_mapBusyStates.find(threadId);
- if (i == m_mapBusyStates.cend()) {
- assert(false);
- return;
- }
- clockid_t clock;
- struct timespec end;
- // Since the specified thread is done now, record how much work it has done for us
- if(pthread_getcpuclockid(threadId, &clock) == 0) {
- clock_gettime(clock, &end);
- AddClocks(timespec2dbl(end) - timespec2dbl(i->second.threadstart), 0, GetTimeOfDay() - i->second.start);
- } else {
- assert(false);
- }
- m_mapBusyStates.erase(threadId);
- }
- void ECSession::GetBusyStates(std::list<BUSYSTATE> *lpStates)
- {
- // this map is very small, since a session only performs one or two functions at a time
- // so the lock time is short, which will block _all_ incoming functions
- lpStates->clear();
- scoped_lock lock(m_hStateLock);
- for (const auto &p : m_mapBusyStates)
- lpStates->push_back(p.second);
- }
- void ECSession::AddClocks(double dblUser, double dblSystem, double dblReal)
- {
- scoped_lock lock(m_hRequestStats);
- m_dblUser += dblUser;
- m_dblSystem += dblSystem;
- m_dblReal += dblReal;
- }
- void ECSession::GetClocks(double *lpdblUser, double *lpdblSystem, double *lpdblReal)
- {
- scoped_lock lock(m_hRequestStats);
- *lpdblUser = m_dblUser;
- *lpdblSystem = m_dblSystem;
- *lpdblReal = m_dblReal;
- }
- void ECSession::GetClientVersion(std::string *lpstrVersion)
- {
- scoped_lock lock(m_hRequestStats);
- lpstrVersion->assign(m_strClientVersion);
- }
- void ECSession::GetClientApp(std::string *lpstrClientApp)
- {
- scoped_lock lock(m_hRequestStats);
- lpstrClientApp->assign(m_strClientApp);
- }
- /**
- * Get the object id of the object specified by the provided entryid.
- * This entryid can either be a short term or 'normal' entryid. If the entryid is a
- * short term entryid, the STE manager for this session will be queried for the object id.
- * If the entryid is a 'normal' entryid, the cache manager / database will be queried.
- *
- * @param[in] lpEntryID The entryid to get an object id for.
- * @param[out] lpulObjId Pointer to an unsigned int that will be set to the returned object id.
- * @param[out] lpbIsShortTerm Optional pointer to a boolean that will be set to true when the entryid
- * is a short term entryid.
- *
- * @retval KCERR_INVALID_PARAMETER lpEntryId or lpulObjId is NULL.
- * @retval KCERR_INVALID_ENTRYID The provided entryid is invalid.
- * @retval KCERR_NOT_FOUND No object was found for the provided entryid.
- */
- ECRESULT ECSession::GetObjectFromEntryId(const entryId *lpEntryId, unsigned int *lpulObjId, unsigned int *lpulEidFlags)
- {
- ECRESULT er;
- unsigned int ulObjId = 0;
- if (lpEntryId == NULL || lpulObjId == NULL)
- return KCERR_INVALID_PARAMETER;
- er = m_lpSessionManager->GetCacheManager()->GetObjectFromEntryId(lpEntryId, &ulObjId);
- if (er != erSuccess)
- return er;
- *lpulObjId = ulObjId;
- if (lpulEidFlags == NULL)
- return erSuccess;
- static_assert(offsetof(EID, usFlags) == offsetof(EID_V0, usFlags),
- "usFlags member not at same position");
- auto d = reinterpret_cast<EID *>(lpEntryId->__ptr);
- if (lpEntryId->__size < 0 ||
- static_cast<size_t>(lpEntryId->__size) < offsetof(EID, usFlags) + sizeof(d->usFlags)) {
- ec_log_err("%s: entryid has size %d; not enough for EID_V1.usFlags",
- __func__, lpEntryId->__size);
- return MAPI_E_CORRUPT_DATA;
- }
- *lpulEidFlags = d->usFlags;
- return erSuccess;
- }
- ECRESULT ECSession::LockObject(unsigned int ulObjId)
- {
- ECRESULT er = erSuccess;
- scoped_lock lock(m_hLocksLock);
- auto res = m_mapLocks.insert(LockMap::value_type(ulObjId, ECObjectLock()));
- if (res.second == true)
- er = m_lpSessionManager->GetLockManager()->LockObject(ulObjId, m_sessionID, &res.first->second);
- return er;
- }
- ECRESULT ECSession::UnlockObject(unsigned int ulObjId)
- {
- ECRESULT er;
- scoped_lock lock(m_hLocksLock);
- auto i = m_mapLocks.find(ulObjId);
- if (i == m_mapLocks.cend())
- return erSuccess;
- er = i->second.Unlock();
- if (er == erSuccess)
- m_mapLocks.erase(i);
- return er;
- }
- size_t ECSession::GetObjectSize()
- {
- size_t ulSize = sizeof(*this);
- ulSize += GetInternalObjectSize();
- ulSize += MEMORY_USAGE_STRING(m_strClientApp) +
- MEMORY_USAGE_STRING(m_strUsername) +
- MEMORY_USAGE_STRING(m_strClientVersion);
- ulSize += MEMORY_USAGE_MAP(m_mapBusyStates.size(), BusyStateMap);
- ulSize += MEMORY_USAGE_MAP(m_mapLocks.size(), LockMap);
- if (m_lpEcSecurity)
- ulSize += m_lpEcSecurity->GetObjectSize();
- // The Table manager size is not callculated here
- // ulSize += GetTableManager()->GetObjectSize();
- return ulSize;
- }
- ECAuthSession::ECAuthSession(const char *src_addr, ECSESSIONID sessionID,
- ECDatabaseFactory *lpDatabaseFactory, ECSessionManager *lpSessionManager,
- unsigned int ulCapabilities) :
- BTSession(src_addr, sessionID, lpDatabaseFactory, lpSessionManager,
- ulCapabilities)
- {
- m_ulSessionTimeout = 30; // authenticate within 30 seconds, or else!
- m_lpUserManagement = new ECUserManagement(this, m_lpSessionManager->GetPluginFactory(), m_lpSessionManager->GetConfig());
- #ifdef HAVE_GSSAPI
- m_gssServerCreds = GSS_C_NO_CREDENTIAL;
- m_gssContext = GSS_C_NO_CONTEXT;
- #endif
- }
- ECAuthSession::~ECAuthSession()
- {
- #ifdef HAVE_GSSAPI
- OM_uint32 status;
- if (m_gssServerCreds)
- gss_release_cred(&status, &m_gssServerCreds);
- if (m_gssContext)
- gss_delete_sec_context(&status, &m_gssContext, GSS_C_NO_BUFFER);
- #endif
- /* Wait until all locks have been closed */
- std::unique_lock<std::mutex> l_thread(m_hThreadReleasedMutex);
- m_hThreadReleased.wait(l_thread, [this](void) { return !IsLocked(); });
- l_thread.unlock();
- if (m_NTLM_pid != -1) {
- int status;
- // close I/O to make ntlm_auth exit
- close(m_stdin);
- close(m_stdout);
- close(m_stderr);
- // wait for process status
- waitpid(m_NTLM_pid, &status, 0);
- ec_log_info("Removing ntlm_auth on pid %d. Exitstatus: %d", m_NTLM_pid, status);
- if (status == -1) {
- ec_log_err(string("System call waitpid failed: ") + strerror(errno));
- } else {
- #ifdef WEXITSTATUS
- if(WIFEXITED(status)) { /* Child exited by itself */
- if(WEXITSTATUS(status))
- ec_log_notice("ntlm_auth exited with non-zero status %d", WEXITSTATUS(status));
- } else if(WIFSIGNALED(status)) { /* Child was killed by a signal */
- ec_log_err("ntlm_auth was killed by signal %d", WTERMSIG(status));
- } else { /* Something strange happened */
- ec_log_err("ntlm_auth terminated abnormally");
- }
- #else
- if (status)
- ec_log_notice("ntlm_auth exited with status %d", status);
- #endif
- }
- }
- delete m_lpUserManagement;
- }
- ECRESULT ECAuthSession::CreateECSession(ECSESSIONGROUPID ecSessionGroupId,
- const std::string &cl_ver, const std::string &cl_app,
- const std::string &cl_app_ver, const std::string &cl_app_misc,
- ECSESSIONID *sessionID, ECSession **lppNewSession)
- {
- ECRESULT er = erSuccess;
- std::unique_ptr<ECSession> lpSession;
- ECSESSIONID newSID;
- if (!m_bValidated)
- return KCERR_LOGON_FAILED;
- CreateSessionID(m_ulClientCapabilities, &newSID);
- // ECAuthSessionOffline creates offline version .. no bOverrideClass construction
- lpSession.reset(new(std::nothrow) ECSession(m_strSourceAddr.c_str(),
- newSID, ecSessionGroupId, m_lpDatabaseFactory,
- m_lpSessionManager, m_ulClientCapabilities,
- m_ulValidationMethod, m_ulConnectingPid,
- cl_ver, cl_app, cl_app_ver, cl_app_misc));
- if (lpSession == nullptr)
- return KCERR_NOT_ENOUGH_MEMORY;
- er = lpSession->GetSecurity()->SetUserContext(m_ulUserID, m_ulImpersonatorID);
- if (er != erSuccess)
- /* User not found anymore, or error in getting groups. */
- return er;
- *sessionID = std::move(newSID);
- *lppNewSession = lpSession.release();
- return erSuccess;
- }
- // This is a standard user/pass login.
- // You always log in as the user you are authenticating with.
- ECRESULT ECAuthSession::ValidateUserLogon(const char* lpszName, const char* lpszPassword, const char* lpszImpersonateUser)
- {
- ECRESULT er;
- if (!lpszName)
- {
- ec_log_err("Invalid argument \"lpszName\" in call to ECAuthSession::ValidateUserLogon()");
- return KCERR_INVALID_PARAMETER;
- }
- if (!lpszPassword) {
- ec_log_err("Invalid argument \"lpszPassword\" in call to ECAuthSession::ValidateUserLogon()");
- return KCERR_INVALID_PARAMETER;
- }
- // SYSTEM can't login with user/pass
- if (strcasecmp(lpszName, KOPANO_ACCOUNT_SYSTEM) == 0)
- return KCERR_NO_ACCESS;
- er = m_lpUserManagement->AuthUserAndSync(lpszName, lpszPassword, &m_ulUserID);
- if(er != erSuccess)
- return er;
- er = ProcessImpersonation(lpszImpersonateUser);
- if (er != erSuccess)
- return er;
- m_bValidated = true;
- m_ulValidationMethod = METHOD_USERPASSWORD;
- return erSuccess;
- }
- // Validate a user through the socket they are connecting through. This has the special feature
- // that you can connect as a different user than you are specifying in the username. For example,
- // you could be connecting as 'root' and being granted access because the kopano-server process
- // is also running as 'root', but you are actually loggin in as user 'user1'.
- ECRESULT ECAuthSession::ValidateUserSocket(int socket, const char* lpszName, const char* lpszImpersonateUser)
- {
- ECRESULT er = erSuccess;
- const char *p = NULL;
- bool allowLocalUsers = false;
- int pid = 0;
- char *ptr = NULL;
- char *localAdminUsers = NULL;
- if (!lpszName)
- {
- ec_log_err("Invalid argument \"lpszName\" in call to ECAuthSession::ValidateUserSocket()");
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if (!lpszImpersonateUser) {
- ec_log_err("Invalid argument \"lpszImpersonateUser\" in call to ECAuthSession::ValidateUserSocket()");
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- p = m_lpSessionManager->GetConfig()->GetSetting("allow_local_users");
- if (p != nullptr && strcasecmp(p, "yes") == 0)
- allowLocalUsers = true;
- // Authentication stage
- localAdminUsers = strdup(m_lpSessionManager->GetConfig()->GetSetting("local_admin_users"));
- struct passwd pwbuf;
- struct passwd *pw;
- uid_t uid;
- char strbuf[1024];
- #ifdef SO_PEERCRED
- struct ucred cr;
- unsigned int cr_len;
- cr_len = sizeof(struct ucred);
- if(getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) != 0 || cr_len != sizeof(struct ucred)) {
- er = KCERR_LOGON_FAILED;
- goto exit;
- }
- uid = cr.uid; // uid is the uid of the user that is connecting
- pid = cr.pid;
- #else // SO_PEERCRED
- #ifdef HAVE_GETPEEREID
- gid_t gid;
- if (getpeereid(socket, &uid, &gid)) {
- er = KCERR_LOGON_FAILED;
- goto exit;
- }
- #else // HAVE_GETPEEREID
- #error I have no way to find out the remote user and I want to cry
- #endif // HAVE_GETPEEREID
- #endif // SO_PEERCRED
- if (geteuid() == uid)
- // User connecting is connecting under same UID as the server is running under, allow this
- goto userok;
- // Lookup user name
- pw = NULL;
- #ifdef HAVE_GETPWNAM_R
- getpwnam_r(lpszName, &pwbuf, strbuf, sizeof(strbuf), &pw);
- #else
- // OpenBSD does not have getpwnam_r() .. FIXME: threading issue!
- pw = getpwnam(lpszName);
- #endif
- if (allowLocalUsers && pw && pw->pw_uid == uid)
- // User connected as himself
- goto userok;
- p = strtok_r(localAdminUsers, WHITESPACE, &ptr);
- while (p) {
- pw = NULL;
- #ifdef HAVE_GETPWNAM_R
- getpwnam_r(p, &pwbuf, strbuf, sizeof(strbuf), &pw);
- #else
- pw = getpwnam(p);
- #endif
- if (pw != nullptr && pw->pw_uid == uid)
- // A local admin user connected - ok
- goto userok;
- p = strtok_r(NULL, WHITESPACE, &ptr);
- }
- er = KCERR_LOGON_FAILED;
- goto exit;
- userok:
- // Check whether user exists in the user database
- er = m_lpUserManagement->ResolveObjectAndSync(OBJECTCLASS_USER, lpszName, &m_ulUserID);
- if (er != erSuccess)
- goto exit;
- er = ProcessImpersonation(lpszImpersonateUser);
- if (er != erSuccess)
- goto exit;
- m_bValidated = true;
- m_ulValidationMethod = METHOD_SOCKET;
- m_ulConnectingPid = pid;
- exit:
- free(localAdminUsers);
- return er;
- }
- ECRESULT ECAuthSession::ValidateUserCertificate(struct soap* soap, const char* lpszName, const char* lpszImpersonateUser)
- {
- ECRESULT er = KCERR_LOGON_FAILED;
- X509 *cert = NULL; // client certificate
- EVP_PKEY *pubkey = NULL; // client public key
- EVP_PKEY *storedkey = NULL;
- int res = -1;
- const char *sslkeys_path = m_lpSessionManager->GetConfig()->GetSetting("sslkeys_path", "", NULL);
- std::unique_ptr<DIR, fs_deleter> dh;
- if (!soap) {
- ec_log_err("Invalid argument \"soap\" in call to ECAuthSession::ValidateUserCertificate()");
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if (!lpszName) {
- ec_log_err("Invalid argument \"lpszName\" in call to ECAuthSession::ValidateUserCertificate()");
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if (!lpszImpersonateUser) {
- ec_log_err("Invalid argument \"lpszImpersonateUser\" in call to ECAuthSession::ValidateUserCertificate()");
- er = KCERR_INVALID_PARAMETER;
- goto exit;
- }
- if (!sslkeys_path || sslkeys_path[0] == '\0') {
- ec_log_warn("No public keys directory defined in sslkeys_path.");
- goto exit;
- }
- cert = SSL_get_peer_certificate(soap->ssl);
- if (!cert) {
- // Windows client without SSL certificate
- ec_log_info("No certificate in SSL connection.");
- goto exit;
- }
- pubkey = X509_get_pubkey(cert); // need to free
- if (!pubkey) {
- // if you get here, please tell me how, 'cause I'd like to know :)
- ec_log_info("No public key in certificate.");
- goto exit;
- }
- dh.reset(opendir(sslkeys_path));
- if (dh == nullptr) {
- ec_log_info("Cannot read directory \"%s\": %s", sslkeys_path, strerror(errno));
- er = KCERR_LOGON_FAILED;
- goto exit;
- }
- for (const struct dirent *dentry = readdir(dh.get());
- dentry != nullptr; dentry = readdir(dh.get())) {
- const char *bname = dentry->d_name;
- auto fullpath = std::string(sslkeys_path) + "/" + bname;
- struct stat sb;
- if (stat(fullpath.c_str(), &sb) < 0 || !S_ISREG(sb.st_mode))
- continue;
- auto biofile = BIO_new_file(fullpath.c_str(), "r");
- if (!biofile) {
- ec_log_info("Unable to create BIO for \"%s\": %s", bname, ERR_error_string(ERR_get_error(), NULL));
- continue;
- }
- storedkey = PEM_read_bio_PUBKEY(biofile, NULL, NULL, NULL);
- if (!storedkey) {
- ec_log_info("Unable to read PUBKEY from \"%s\": %s", bname, ERR_error_string(ERR_get_error(), NULL));
- BIO_free(biofile);
- continue;
- }
- res = EVP_PKEY_cmp(pubkey, storedkey);
- BIO_free(biofile);
- EVP_PKEY_free(storedkey);
- if (res <= 0) {
- ec_log_info("Certificate \"%s\" does not match.", bname);
- } else {
- er = erSuccess;
- ec_log_info("Accepted certificate \"%s\" from client.", bname);
- break;
- }
- }
- if (er != erSuccess)
- goto exit;
- // Check whether user exists in the user database
- er = m_lpUserManagement->ResolveObjectAndSync(OBJECTCLASS_USER, lpszName, &m_ulUserID);
- if (er != erSuccess)
- goto exit;
- er = ProcessImpersonation(lpszImpersonateUser);
- if (er != erSuccess)
- goto exit;
- m_bValidated = true;
- m_ulValidationMethod = METHOD_SSL_CERT;
- exit:
- if (cert)
- X509_free(cert);
- if (pubkey)
- EVP_PKEY_free(pubkey);
- return er;
- }
- #define NTLMBUFFER 8192
- ECRESULT ECAuthSession::ValidateSSOData(struct soap* soap, const char* lpszName, const char* lpszImpersonateUser, const char* szClientVersion, const char *szClientApp, const char *szClientAppVersion, const char *szClientAppMisc, const struct xsd__base64Binary* lpInput, struct xsd__base64Binary **lppOutput)
- {
- ECRESULT er = KCERR_INVALID_PARAMETER;
- if (!soap) {
- ec_log_err("Invalid argument \"soap\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- if (!lpszName) {
- ec_log_err("Invalid argument \"lpszName\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- if (!lpszImpersonateUser) {
- ec_log_err("Invalid argument \"lpszImpersonateUser\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- if (!szClientVersion) {
- ec_log_err("Invalid argument \"szClientVersion\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- if (!szClientApp) {
- ec_log_err("Invalid argument \"szClientApp\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- if (!lpInput) {
- ec_log_err("Invalid argument \"lpInput\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- if (!lppOutput) {
- ec_log_err("Invalid argument \"lppOutput\" in call to ECAuthSession::ValidateSSOData()");
- return er;
- }
- er = KCERR_LOGON_FAILED;
- // first NTLM package starts with that signature, continues are detected by the filedescriptor
- if (m_NTLM_pid != -1 || strncmp((const char*)lpInput->__ptr, "NTLM", 4) == 0)
- er = ValidateSSOData_NTLM(soap, lpszName, szClientVersion, szClientApp, szClientAppVersion, szClientAppMisc, lpInput, lppOutput);
- else
- er = ValidateSSOData_KRB5(soap, lpszName, szClientVersion, szClientApp, szClientAppVersion, szClientAppMisc, lpInput, lppOutput);
- if (er != erSuccess)
- return er;
- er = ProcessImpersonation(lpszImpersonateUser);
- if (er != erSuccess)
- return er;
- return erSuccess;
- }
- #ifdef HAVE_GSSAPI
- const char gss_display_status_fail_message[] = "Call to gss_display_status failed. Reason: ";
- static ECRESULT LogKRB5Error_2(const char *msg, OM_uint32 code, OM_uint32 type)
- {
- gss_buffer_desc gssMessage = GSS_C_EMPTY_BUFFER;
- OM_uint32 status = 0;
- OM_uint32 context = 0;
- if (msg == NULL) {
- ec_log_err("Invalid argument \"msg\" in call to ECAuthSession::LogKRB5Error()");
- return KCERR_INVALID_PARAMETER;
- }
- ECRESULT retval = KCERR_CALL_FAILED;
- do {
- OM_uint32 result = gss_display_status(&status, code, type, GSS_C_NULL_OID, &context, &gssMessage);
- switch (result) {
- case GSS_S_COMPLETE:
- ec_log_warn("%s: %s", msg, (char*)gssMessage.value);
- retval = erSuccess;
- break;
- case GSS_S_BAD_MECH:
- ec_log_warn("%s: %s", gss_display_status_fail_message, "unsupported mechanism type was requested.");
- retval = KCERR_CALL_FAILED;
- break;
- case GSS_S_BAD_STATUS:
- ec_log_warn("%s: %s", gss_display_status_fail_message, "status value was not recognized, or the status type was neither GSS_C_GSS_CODE nor GSS_C_MECH_CODE.");
- retval = KCERR_CALL_FAILED;
- break;
- }
- gss_release_buffer(&status, &gssMessage);
- } while (context != 0);
- return retval;
- }
- ECRESULT ECAuthSession::LogKRB5Error(const char* msg, OM_uint32 major, OM_uint32 minor)
- {
- if (!msg) {
- ec_log_err("Invalid argument \"msg\" in call to ECAuthSession::LogKRB5Error()");
- return KCERR_INVALID_PARAMETER;
- }
- LogKRB5Error_2(msg, major, GSS_C_GSS_CODE);
- return LogKRB5Error_2(msg, minor, GSS_C_MECH_CODE);
- }
- #endif
- ECRESULT ECAuthSession::ValidateSSOData_KRB5(struct soap* soap, const char* lpszName, const char* szClientVersion, const char* szClientApp, const char *szClientAppVersion, const char *szClientAppMisc, const struct xsd__base64Binary* lpInput, struct xsd__base64Binary** lppOutput)
- {
- ECRESULT er = KCERR_INVALID_PARAMETER;
- #ifndef HAVE_GSSAPI
- ec_log_err("Incoming Kerberos request, but this server was build without GSSAPI support.");
- #else
- OM_uint32 retval, status;
- gss_name_t gssServername = GSS_C_NO_NAME;
- gss_buffer_desc gssInputBuffer = GSS_C_EMPTY_BUFFER;
- const char *szHostname = NULL;
- std::string principal;
- gss_name_t gssUsername = GSS_C_NO_NAME;
- gss_buffer_desc gssUserBuffer = GSS_C_EMPTY_BUFFER;
- gss_buffer_desc gssOutputToken = GSS_C_EMPTY_BUFFER;
- std::string strUsername;
- string::size_type pos;
- struct xsd__base64Binary *lpOutput = NULL;
- if (!soap) {
- ec_log_err("Invalid argument \"soap\" in call to ECAuthSession::ValidateSSOData_KRB5()");
- goto exit;
- }
- if (!lpszName) {
- ec_log_err("Invalid argument \"lpszName\" in call to ECAuthSession::ValidateSSOData_KRB5()");
- goto exit;
- }
- if (!szClientVersion) {
- ec_log_err("Invalid argument \"zClientVersionin\" in call to ECAuthSession::ValidateSSOData_KRB5()");
- goto exit;
- }
- if (!szClientApp) {
- ec_log_err("Invalid argument \"szClientApp\" in call to ECAuthSession::ValidateSSOData_KRB5()");
- goto exit;
- }
- if (!lpInput) {
- ec_log_err("Invalid argument \"lpInput\" in call to ECAuthSession::ValidateSSOData_KRB5()");
- goto exit;
- }
- if (!lppOutput) {
- ec_log_err("Invalid argument \"lppOutput\" in call to ECAuthSession::ValidateSSOData_KRB5()");
- goto exit;
- }
- er = KCERR_LOGON_FAILED;
- if (m_gssServerCreds == GSS_C_NO_CREDENTIAL) {
- m_gssContext = GSS_C_NO_CONTEXT;
- // ECServer made sure this setting option always contains the best hostname
- // If it's not there, that's unacceptable.
- szHostname = m_lpSessionManager->GetConfig()->GetSetting("server_hostname");
- if (!szHostname || szHostname[0] == '\0') {
- ec_log_crit("Hostname not found, required for Kerberos");
- goto exit;
- }
- principal = "kopano@";
- principal += szHostname;
- ec_log_debug("Kerberos principal: %s", principal.c_str());
- gssInputBuffer.value = (void*)principal.data();
- gssInputBuffer.length = principal.length() + 1;
- retval = gss_import_name(&status, &gssInputBuffer, GSS_C_NT_HOSTBASED_SERVICE, &gssServername);
- if (retval != GSS_S_COMPLETE) {
- LogKRB5Error("Unable to import server name", retval, status);
- goto exit;
- }
- retval = gss_acquire_cred(&status, gssServername, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &m_gssServerCreds, NULL, NULL);
- if (retval != GSS_S_COMPLETE) {
- LogKRB5Error("Unable to acquire credentials handle", retval, status);
- goto exit;
- }
- }
- gssInputBuffer.length = lpInput->__size;
- gssInputBuffer.value = lpInput->__ptr;
- retval = gss_accept_sec_context(&status, &m_gssContext, m_gssServerCreds, &gssInputBuffer, GSS_C_NO_CHANNEL_BINDINGS, &gssUsername, NULL, &gssOutputToken, NULL, NULL, NULL);
- if (gssOutputToken.length) {
- // we need to send data back to the client, no need to consider retval
- lpOutput = s_alloc<struct xsd__base64Binary>(soap);
- lpOutput->__size = gssOutputToken.length;
- lpOutput->__ptr = s_alloc<unsigned char>(soap, gssOutputToken.length);
- memcpy(lpOutput->__ptr, gssOutputToken.value, gssOutputToken.length);
- gss_release_buffer(&status, &gssOutputToken);
- }
- if (retval == GSS_S_CONTINUE_NEEDED) {
- er = KCERR_SSO_CONTINUE;
- goto exit;
- } else if (retval != GSS_S_COMPLETE) {
- LogKRB5Error("Unable to accept security context", retval, status);
- ZLOG_AUDIT(m_lpSessionManager->GetAudit(), "authenticate failed user='%s' from='%s' method='kerberos sso' program='%s'",
- lpszName, soap->host, szClientApp);
- goto exit;
- }
- retval = gss_display_name(&status, gssUsername, &gssUserBuffer, NULL);
- if (retval) {
- LogKRB5Error("Unable to convert username", retval, status);
- goto exit;
- }
- ec_log_debug("Kerberos username: %s", static_cast<const char *>(gssUserBuffer.value));
- // kerberos returns: username@REALM, username is case-insensitive
- strUsername.assign((char*)gssUserBuffer.value, gssUserBuffer.length);
- pos = strUsername.find_first_of('@');
- if (pos != string::npos)
- strUsername.erase(pos);
- if (strcasecmp(strUsername.c_str(), lpszName) == 0) {
- er = m_lpUserManagement->ResolveObjectAndSync(ACTIVE_USER, lpszName, &m_ulUserID);
- // don't check NONACTIVE, since those shouldn't be able to login
- if(er != erSuccess)
- goto exit;
- m_bValidated = true;
- m_ulValidationMethod = METHOD_SSO;
- ec_log_info("Kerberos Single Sign-On: User \"%s\" authenticated", lpszName);
- ZLOG_AUDIT(m_lpSessionManager->GetAudit(), "authenticate ok user='%s' from='%s' method='kerberos sso' program='%s'",
- lpszName, soap->host, szClientApp);
- } else {
- ec_log_err("Kerberos username \"%s\" authenticated, but user \"%s\" requested.", (char*)gssUserBuffer.value, lpszName);
- ZLOG_AUDIT(m_lpSessionManager->GetAudit(), "authenticate spoofed user='%s' requested='%s' from='%s' method='kerberos sso' program='%s'",
- static_cast<char *>(gssUserBuffer.value), lpszName, soap->host, szClientApp);
- }
- exit:
- if (gssUserBuffer.length)
- gss_release_buffer(&status, &gssUserBuffer);
- if (gssOutputToken.length)
- gss_release_buffer(&status, &gssOutputToken);
- if (gssUsername != GSS_C_NO_NAME)
- gss_release_name(&status, &gssUsername);
- if (gssServername != GSS_C_NO_NAME)
- gss_release_name(&status, &gssServername);
- if (lppOutput != nullptr)
- *lppOutput = lpOutput;
- #endif
- return er;
- }
- ECRESULT ECAuthSession::ValidateSSOData_NTLM(struct soap* soap, const char* lpszName, const char* szClientVersion, const char* szClientApp, const char *szClientAppVersion, const char *szClientAppMisc, const struct xsd__base64Binary* lpInput, struct xsd__base64Binary **lppOutput)
- {
- ECRESULT er = KCERR_INVALID_PARAMETER;
- struct xsd__base64Binary *lpOutput = NULL;
- char buffer[NTLMBUFFER];
- std::string strEncoded, strDecoded, strAnswer;
- ssize_t bytes = 0;
- char separator = '\\'; // get config version
- struct pollfd pollfd[2] = {{m_stdout, POLLIN | POLLRDHUP}, {m_stderr, POLLIN | POLLRDHUP}};
- if (!soap) {
- ec_log_err("Invalid argument \"soap\" in call to ECAuthSession::ValidateSSOData_NTLM()");
- return er;
- }
- if (!lpszName) {
- ec_log_err("Invalid argument \"lpszName\" in call to ECAuthSession::ValidateSSOData_NTLM()");
- return er;
- }
- if (!szClientVersion) {
- ec_log_err("Invalid argument \"zClientVersionin\" in call to ECAuthSession::ValidateSSOData_NTLM()");
- return er;
- }
- if (!szClientApp) {
- ec_log_err("Invalid argument \"szClientApp\" in call to ECAuthSession::ValidateSSOData_NTLM()");
- return er;
- }
- if (!lpInput) {
- ec_log_err("Invalid argument \"lpInput\" in call to ECAuthSession::ValidateSSOData_NTLM()");
- return er;
- }
- if (!lppOutput) {
- ec_log_err("Invalid argument \"lppOutput\" in call to ECAuthSession::ValidateSSOData_NTLM()");
- return er;
- }
- er = KCERR_LOGON_FAILED;
- strEncoded = base64_encode(lpInput->__ptr, lpInput->__size);
- errno = 0;
- if (m_NTLM_pid == -1) {
- // start new ntlmauth pipe
- // TODO: configurable path?
- if (pipe(m_NTLM_stdin) == -1 || pipe(m_NTLM_stdout) == -1 || pipe(m_NTLM_stderr) == -1) {
- ec_log_crit(string("Unable to create communication pipes for ntlm_auth: ") + strerror(errno));
- return er;
- }
- /*
- * Why are we using vfork() ?
- *
- * You might as well use fork() here but vfork() is much faster in our case; this is because vfork() doesn't actually duplicate
- * any pages, expecting you to call execl(). Watch out though, since data changes done in the client process before execl() WILL
- * affect the mother process. (however, the file descriptor table is correctly cloned)
- *
- * The reason fork() is slow is that even though it is doing a Copy-On-Write copy, it still needs to do some page-copying to set up your
- * process. This copying time increases with memory usage of the mother process; in fact, running 200 forks() on a process occupying
- * 512MB of memory takes 15 seconds, while the same vfork()/exec() loop takes under .5 of a second.
- *
- * If vfork() is not available, or is broken on another platform, it is safe to simply replace it with fork(), but it will be quite slow!
- */
- m_NTLM_pid = vfork();
- if (m_NTLM_pid == -1) {
- // broken
- ec_log_crit(string("Unable to start new process for ntlm_auth: ") + strerror(errno));
- return er;
- } else if (m_NTLM_pid == 0) {
- // client
- int j, k;
- close(m_NTLM_stdin[1]);
- close(m_NTLM_stdout[0]);
- close(m_NTLM_stderr[0]);
- dup2(m_NTLM_stdin[0], 0);
- dup2(m_NTLM_stdout[1], 1);
- dup2(m_NTLM_stderr[1], 2);
- // close all other open file descriptors, so ntlm doesn't keep the kopano-server sockets open
- j = getdtablesize();
- for (k = 3; k < j; ++k)
- close(k);
- execl("/bin/sh", "sh", "-c", "ntlm_auth -d0 --helper-protocol=squid-2.5-ntlmssp", NULL);
- ec_log_crit(string("Cannot start ntlm_auth: ") + strerror(errno));
- _exit(2);
- } else {
- // parent
- ec_log_info("New ntlm_auth started on pid %d", m_NTLM_pid);
- close(m_NTLM_stdin[0]);
- close(m_NTLM_stdout[1]);
- close(m_NTLM_stderr[1]);
- m_stdin = ec_relocate_fd(m_NTLM_stdin[1]);
- m_stdout = ec_relocate_fd(m_NTLM_stdout[0]);
- m_stderr = ec_relocate_fd(m_NTLM_stderr[0]);
- // Yo! Refresh!
- write(m_stdin, "YR ", 3);
- write(m_stdin, strEncoded.c_str(), strEncoded.length());
- write(m_stdin, "\n", 1);
- }
- } else {
- // Knock knock! who's there?
- write(m_stdin, "KK ", 3);
- write(m_stdin, strEncoded.c_str(), strEncoded.length());
- write(m_stdin, "\n", 1);
- }
- memset(buffer, 0, NTLMBUFFER);
- retry:
- pollfd[0].revents = pollfd[1].revents = 0;
- int ret = poll(pollfd, 2, 10 * 1000); // timeout of 10 seconds before ntlm_auth can respond too large?
- if (ret < 0) {
- if (errno == EINTR)
- goto retry;
- ec_log_err(string("Error while waiting for data from ntlm_auth: ") + strerror(errno));
- return er;
- }
- if (ret == 0) {
- // timeout
- ec_log_err("Timeout while reading from ntlm_auth");
- return er;
- }
- // stderr is optional, and always written first
- if (pollfd[1].revents & (POLLIN | POLLRDHUP)) {
- // log stderr of ntlm_auth to logfile (loop?)
- bytes = read(m_stderr, buffer, NTLMBUFFER-1);
- if (bytes >= 0)
- buffer[bytes] = '\0';
- // print in lower level. if ntlm_auth was not installed (kerberos only environment), you won't care that ntlm_auth doesn't work.
- // login error is returned to the client, which was expected anyway.
- ec_log_notice(string("Received error from ntlm_auth:\n") + buffer);
- return er;
- }
- // stdout is mandatory, so always read from this pipe
- memset(buffer, 0, NTLMBUFFER);
- bytes = read(m_stdout, buffer, NTLMBUFFER-1);
- if (bytes < 0) {
- ec_log_err(string("Unable to read data from ntlm_auth: ") + strerror(errno));
- return er;
- } else if (bytes == 0) {
- ec_log_err("Nothing read from ntlm_auth");
- return er;
- }
- if (buffer[bytes-1] == '\n')
- /*
- * Strip newline right away, it is not useful for logging,
- * nor for base64_decode.
- */
- buffer[--bytes] = '\0';
- if (bytes < 2) {
- /* Ensure buffer[0]==.. && buffer[1]==.. is valid to do */
- ec_log_err("Short reply from ntlm_auth");
- return er;
- }
- if (bytes >= 3)
- /*
- * Extract response text (if any) following the reply code
- * (and space). Else left empty.
- */
- strAnswer.assign(buffer + 3, bytes - 3);
- if (buffer[0] == 'B' && buffer[1] == 'H') {
- // Broken Helper
- ec_log_err("Incorrect data fed to ntlm_auth");
- return er;
- } else if (buffer[0] == 'T' && buffer[1] == 'T') {
- // Try This
- strDecoded = base64_decode(strAnswer);
- lpOutput = s_alloc<struct xsd__base64Binary>(soap);
- lpOutput->__size = strDecoded.length();
- lpOutput->__ptr = s_alloc<unsigned char>(soap, strDecoded.length());
- memcpy(lpOutput->__ptr, strDecoded.data(), strDecoded.length());
- er = KCERR_SSO_CONTINUE;
- } else if (buffer[0] == 'A' && buffer[1] == 'F') {
- // Authentication Fine
- // Samba default runs in UTF-8 and setting 'unix charset' to windows-1252 in the samba config will break ntlm_auth
- // convert the username before we use it in Kopano
- ECIConv iconv("windows-1252", "utf-8");
- if (!iconv.canConvert()) {
- ec_log_crit("Problem setting up windows-1252 to utf-8 converter");
- return er;
- }
- strAnswer = iconv.convert(strAnswer);
- ec_log_info("Found username (%s)", strAnswer.c_str());
- // if the domain separator is not found, assume we only have the username (samba)
- string::size_type pos = strAnswer.find_first_of(separator);
- if (pos != string::npos) {
- ++pos;
- strAnswer.assign(strAnswer, pos, strAnswer.length()-pos);
- }
- // Check whether user exists in the user database
- er = m_lpUserManagement->ResolveObjectAndSync(ACTIVE_USER, (char *)strAnswer.c_str(), &m_ulUserID);
- // don't check NONACTIVE, since those shouldn't be able to login
- if(er != erSuccess)
- return er;
- if (strcasecmp(lpszName, strAnswer.c_str()) != 0) {
- // cannot open another user without password
- // or should we check permissions ?
- ec_log_warn("Single Sign-On: User \"%s\" authenticated, but user \"%s\" requested.", strAnswer.c_str(), lpszName);
- ZLOG_AUDIT(m_lpSessionManager->GetAudit(), "authenticate spoofed user='%s' requested='%s' from='%s' method='ntlm sso' program='%s'",
- strAnswer.c_str(), lpszName, soap->host, szClientApp);
- er = KCERR_LOGON_FAILED;
- } else {
- m_bValidated = true;
- m_ulValidationMethod = METHOD_SSO;
- er = erSuccess;
- ec_log_info("Single Sign-On: User \"%s\" authenticated", strAnswer.c_str());
- ZLOG_AUDIT(m_lpSessionManager->GetAudit(), "authenticate ok user='%s' from='%s' method='ntlm sso' program='%s'",
- lpszName, soap->host, szClientApp);
- }
- } else if (buffer[0] == 'N' && buffer[1] == 'A') {
- // Not Authenticated
- ec_log_info("Requested user \"%s\" denied. Not authenticated: \"%s\"", lpszName, strAnswer.c_str());
- ZLOG_AUDIT(m_lpSessionManager->GetAudit(), "authenticate failed user='%s' from='%s' method='ntlm sso' program='%s'",
- lpszName, soap->host, szClientApp);
- er = KCERR_LOGON_FAILED;
- } else {
- // unknown response?
- ec_log_err("Unknown response from ntlm_auth: %.*s", static_cast<int>(bytes), buffer);
- return KCERR_CALL_FAILED;
- }
- *lppOutput = lpOutput;
- return er;
- }
- #undef NTLMBUFFER
- ECRESULT ECAuthSession::ProcessImpersonation(const char* lpszImpersonateUser)
- {
- if (lpszImpersonateUser == NULL || *lpszImpersonateUser == '\0') {
- m_ulImpersonatorID = EC_NO_IMPERSONATOR;
- return erSuccess;
- }
- m_ulImpersonatorID = m_ulUserID;
- return m_lpUserManagement->ResolveObjectAndSync(OBJECTCLASS_USER,
- lpszImpersonateUser, &m_ulUserID);
- }
- size_t ECAuthSession::GetObjectSize()
- {
- size_t ulSize = sizeof(*this);
- return ulSize;
- }
- } /* namespace */
|