123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026 |
- /*
- * 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 <exception>
- #include <iostream>
- #include <mutex>
- #include <string>
- #include <stdexcept>
- #include <sys/types.h>
- #include <pwd.h>
- #include <sstream>
- #include <cassert>
- #include <sys/stat.h>
- #include <grp.h>
- #include <crypt.h>
- #include <set>
- #include <iterator>
- #ifdef HAVE_SHADOW_H
- #include <shadow.h>
- #include <cerrno>
- #endif
- #include <kopano/EMSAbTag.h>
- #include <kopano/ECConfig.h>
- #include <kopano/ECDefs.h>
- #include <kopano/ECLogger.h>
- #include <kopano/ECPluginSharedData.h>
- #include <kopano/lockhelper.hpp>
- #include <kopano/stringutil.h>
- using namespace std;
- #include "UnixUserPlugin.h"
- #include <kopano/ecversion.h>
- /**
- * static buffer size for getpwnam_r() calls etc.
- * needs to be big enough for all strings in the passwd/group/spwd struct:
- */
- #define PWBUFSIZE 16384
- extern "C" {
- UserPlugin *getUserPluginInstance(std::mutex &pluginlock,
- ECPluginSharedData *shareddata)
- {
- return new UnixUserPlugin(pluginlock, shareddata);
- }
- void deleteUserPluginInstance(UserPlugin *up) {
- delete up;
- }
- int getUserPluginVersion() {
- return PROJECT_VERSION_REVISION;
- }
- }
- UnixUserPlugin::UnixUserPlugin(std::mutex &pluginlock,
- ECPluginSharedData *shareddata) :
- DBPlugin(pluginlock, shareddata)
- {
- const configsetting_t lpDefaults [] = {
- { "fullname_charset", "iso-8859-15" }, // US-ASCII compatible with support for high characters
- { "default_domain", "localhost" }, // no sane default
- { "non_login_shell", "/bin/false", CONFIGSETTING_RELOADABLE }, // create a non-login box when a user has this shell
- { "min_user_uid", "1000", CONFIGSETTING_RELOADABLE },
- { "max_user_uid", "10000", CONFIGSETTING_RELOADABLE },
- { "except_user_uids", "", CONFIGSETTING_RELOADABLE },
- { "min_group_gid", "1000", CONFIGSETTING_RELOADABLE },
- { "max_group_gid", "10000", CONFIGSETTING_RELOADABLE },
- { "except_group_gids", "", CONFIGSETTING_RELOADABLE },
- { NULL, NULL }
- };
- m_config = shareddata->CreateConfig(lpDefaults);
- if (!m_config)
- throw runtime_error(string("Not a valid configuration file."));
- if (m_bHosted)
- throw notsupported("Hosted Kopano not supported when using the Unix Plugin");
- if (m_bDistributed)
- throw notsupported("Distributed Kopano not supported when using the Unix Plugin");
- }
- UnixUserPlugin::~UnixUserPlugin()
- {
- delete m_iconv;
- }
- void UnixUserPlugin::InitPlugin() {
- DBPlugin::InitPlugin();
- // we only need unix_charset -> kopano charset
- m_iconv = new ECIConv("utf-8", m_config->GetSetting("fullname_charset"));
- if (!m_iconv -> canConvert())
- throw runtime_error(string("Cannot setup charset converter, check \"fullname_charset\" in cfg"));
- }
- void UnixUserPlugin::findUserID(const string &id, struct passwd *pwd, char *buffer)
- {
- struct passwd *pw = NULL;
- uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
- uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
- vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
- objectid_t objectid;
- int ret = getpwuid_r(atoi(id.c_str()), pwd, buffer, PWBUFSIZE, &pw);
- if (ret != 0)
- errnoCheck(id, ret);
- if (pw == NULL)
- throw objectnotfound(id);
- if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
- throw objectnotfound(id);
-
- for (unsigned int i = 0; i < exceptuids.size(); ++i)
- if (pw->pw_uid == fromstring<const std::string, uid_t>(exceptuids[i]))
- throw objectnotfound(id);
- }
- void UnixUserPlugin::findUser(const string &name, struct passwd *pwd, char *buffer)
- {
- struct passwd *pw = NULL;
- uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
- uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
- vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
- objectid_t objectid;
- int ret = getpwnam_r(name.c_str(), pwd, buffer, PWBUFSIZE, &pw);
- if (ret != 0)
- errnoCheck(name, ret);
- if (pw == NULL)
- throw objectnotfound(name);
- if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
- throw objectnotfound(name);
-
- for (unsigned int i = 0; i < exceptuids.size(); ++i)
- if (pw->pw_uid == fromstring<const std::string, uid_t>(exceptuids[i]))
- throw objectnotfound(name);
- }
- void UnixUserPlugin::findGroupID(const string &id, struct group *grp, char *buffer)
- {
- struct group *gr = NULL;
- gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
- gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
- vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
- objectid_t objectid;
- int ret = getgrgid_r(atoi(id.c_str()), grp, buffer, PWBUFSIZE, &gr);
- if (ret != 0)
- errnoCheck(id, ret);
- if (gr == NULL)
- throw objectnotfound(id);
- if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
- throw objectnotfound(id);
- for (unsigned int i = 0; i < exceptgids.size(); ++i)
- if (gr->gr_gid == fromstring<const std::string, gid_t>(exceptgids[i]))
- throw objectnotfound(id);
- }
- void UnixUserPlugin::findGroup(const string &name, struct group *grp, char *buffer)
- {
- struct group *gr = NULL;
- gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
- gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
- vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
- objectid_t objectid;
- int ret = getgrnam_r(name.c_str(), grp, buffer, PWBUFSIZE, &gr);
- if (ret != 0)
- errnoCheck(name, ret);
- if (gr == NULL)
- throw objectnotfound(name);
- if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
- throw objectnotfound(name);
- for (unsigned int i = 0; i < exceptgids.size(); ++i)
- if (gr->gr_gid == fromstring<const std::string, gid_t>(exceptgids[i]))
- throw objectnotfound(name);
- }
- objectsignature_t UnixUserPlugin::resolveUserName(const string &name)
- {
- char buffer[PWBUFSIZE];
- struct passwd pws;
- const char *lpszNonActive = m_config->GetSetting("non_login_shell");
- objectid_t objectid;
- findUser(name, &pws, buffer);
- if (strcmp(pws.pw_shell, lpszNonActive) != 0)
- objectid = objectid_t(tostring(pws.pw_uid), ACTIVE_USER);
- else
- objectid = objectid_t(tostring(pws.pw_uid), NONACTIVE_USER);
- return objectsignature_t(objectid, getDBSignature(objectid) + pws.pw_gecos + pws.pw_name);
- }
- objectsignature_t UnixUserPlugin::resolveGroupName(const string &name)
- {
- char buffer[PWBUFSIZE];
- struct group grp;
- objectid_t objectid;
-
- findGroup(name, &grp, buffer);
- objectid = objectid_t(tostring(grp.gr_gid), DISTLIST_SECURITY);
- return objectsignature_t(objectid, grp.gr_name);
- }
- objectsignature_t UnixUserPlugin::resolveName(objectclass_t objclass, const string &name, const objectid_t &company)
- {
- objectsignature_t user;
- objectsignature_t group;
- if (company.id.empty()) {
- LOG_PLUGIN_DEBUG("%s Class %x, Name %s", __FUNCTION__, objclass, name.c_str());
- } else {
- LOG_PLUGIN_DEBUG("%s Class %x, Name %s, Company %s", __FUNCTION__, objclass, name.c_str(), company.id.c_str());
- }
- switch (OBJECTCLASS_TYPE(objclass)) {
- case OBJECTTYPE_UNKNOWN:
- // Caller doesn't know what he is looking for, try searching through
- // users and groups. Note that 1 function _must_ fail because otherwise
- // we have a duplicate entry.
- try {
- user = resolveUserName(name);
- } catch (std::exception &e) {
- // object is not a user
- }
- try {
- group = resolveGroupName(name);
- } catch (std::exception &e) {
- // object is not a group
- }
- if (!user.id.id.empty()) {
- // Object is both user and group
- if (!group.id.id.empty())
- throw toomanyobjects(name);
- return user;
- } else {
- // Object is neither user not group
- if (group.id.id.empty())
- throw objectnotfound(name);
- return group;
- }
- case OBJECTTYPE_MAILUSER:
- return resolveUserName(name);
- case OBJECTTYPE_DISTLIST:
- return resolveGroupName(name);
- default:
- throw runtime_error("Unknown object type " + stringify(objclass));
- }
- }
- objectsignature_t UnixUserPlugin::authenticateUser(const string &username, const string &password, const objectid_t &companyname) {
- struct passwd pws, *pw = NULL;
- char buffer[PWBUFSIZE];
- uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
- uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
- vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
- std::unique_ptr<struct crypt_data> cryptdata;
- std::unique_ptr<objectdetails_t> ud;
- objectid_t objectid;
- const char *crpw = NULL;
- cryptdata.reset(new struct crypt_data); // malloc because it is > 128K !
- memset(cryptdata.get(), 0, sizeof(struct crypt_data));
- int ret = getpwnam_r(username.c_str(), &pws, buffer, PWBUFSIZE, &pw);
- if (ret != 0)
- errnoCheck(username, ret);
- if (pw == NULL)
- throw objectnotfound(username);
- if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
- throw objectnotfound(username);
-
- for (unsigned i = 0; i < exceptuids.size(); ++i)
- if (pw->pw_uid == fromstring<const std::string, uid_t>(exceptuids[i]))
- throw objectnotfound(username);
- if (strcmp(pw->pw_shell, m_config->GetSetting("non_login_shell")) == 0)
- throw login_error("Non-active user disallowed to login");
- ud = objectdetailsFromPwent(pw);
- crpw = crypt_r(password.c_str(), ud->GetPropString(OB_PROP_S_PASSWORD).c_str(), cryptdata.get());
- if (!crpw || strcmp(crpw, ud->GetPropString(OB_PROP_S_PASSWORD).c_str()))
- throw login_error("Trying to authenticate failed: wrong username or password");
- objectid = objectid_t(tostring(pw->pw_uid), ACTIVE_USER);
- return objectsignature_t(objectid, getDBSignature(objectid) + pw->pw_gecos + pw->pw_name);
- }
- bool UnixUserPlugin::matchUserObject(struct passwd *pw, const string &match, unsigned int ulFlags)
- {
- string email;
- bool matched = false;
- // username or fullname
- if(ulFlags & EMS_AB_ADDRESS_LOOKUP) {
- matched =
- (strcasecmp(pw->pw_name, (char*)match.c_str()) == 0) ||
- (strcasecmp((char*)m_iconv->convert(pw->pw_gecos).c_str(), (char*)match.c_str()) == 0);
- } else {
- matched =
- (strncasecmp(pw->pw_name, (char*)match.c_str(), match.size()) == 0) ||
- (strncasecmp((char*)m_iconv->convert(pw->pw_gecos).c_str(), (char*)match.c_str(), match.size()) == 0);
- }
- if (matched)
- return matched;
- email = string(pw->pw_name) + "@" + m_config->GetSetting("default_domain");
- if(ulFlags & EMS_AB_ADDRESS_LOOKUP)
- matched = (email == match);
- else
- matched = (strncasecmp((char*)email.c_str(), (char*)match.c_str(), match.size()) == 0);
- return matched;
- }
- bool UnixUserPlugin::matchGroupObject(struct group *gr, const string &match, unsigned int ulFlags)
- {
- bool matched = false;
- if(ulFlags & EMS_AB_ADDRESS_LOOKUP)
- matched = strcasecmp(gr->gr_name, (char*)match.c_str()) == 0;
- else
- matched = strncasecmp(gr->gr_name, (char*)match.c_str(), match.size()) == 0;
- return matched;
- }
- std::unique_ptr<signatures_t>
- UnixUserPlugin::getAllUserObjects(const std::string &match,
- unsigned int ulFlags)
- {
- std::unique_ptr<signatures_t> objectlist(new signatures_t());
- char buffer[PWBUFSIZE];
- struct passwd pws, *pw = NULL;
- uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
- uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
- const char *lpszNonActive = m_config->GetSetting("non_login_shell");
- vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
- set<uid_t> exceptuidset;
- objectid_t objectid;
- transform(exceptuids.begin(), exceptuids.end(), inserter(exceptuidset, exceptuidset.begin()), fromstring<const std::string,uid_t>);
- setpwent();
- while (true) {
- if (getpwent_r(&pws, buffer, PWBUFSIZE, &pw) != 0)
- break;
- if (pw == NULL)
- break;
- // system users don't have kopano accounts
- if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
- continue;
- if (exceptuidset.find(pw->pw_uid) != exceptuidset.end())
- continue;
- if (!match.empty() && !matchUserObject(pw, match, ulFlags))
- continue;
- if (strcmp(pw->pw_shell, lpszNonActive) != 0)
- objectid = objectid_t(tostring(pw->pw_uid), ACTIVE_USER);
- else
- objectid = objectid_t(tostring(pw->pw_uid), NONACTIVE_USER);
- objectlist->push_back(objectsignature_t(objectid, getDBSignature(objectid) + pw->pw_gecos + pw->pw_name));
- }
- endpwent();
- return objectlist;
- }
- std::unique_ptr<signatures_t>
- UnixUserPlugin::getAllGroupObjects(const std::string &match,
- unsigned int ulFlags)
- {
- std::unique_ptr<signatures_t> objectlist(new signatures_t());
- char buffer[PWBUFSIZE];
- struct group grs, *gr = NULL;
- gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
- gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
- vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
- set<gid_t> exceptgidset;
- objectid_t objectid;
- transform(exceptgids.begin(), exceptgids.end(), inserter(exceptgidset, exceptgidset.begin()), fromstring<const std::string,uid_t>);
- setgrent();
- while (true) {
- if (getgrent_r(&grs, buffer, PWBUFSIZE, &gr) != 0)
- break;
- if (gr == NULL)
- break;
- // system groups don't have kopano accounts
- if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
- continue;
- if (exceptgidset.find(gr->gr_gid) != exceptgidset.end())
- continue;
- if (!match.empty() && !matchGroupObject(gr, match, ulFlags))
- continue;
- objectid = objectid_t(tostring(gr->gr_gid), DISTLIST_SECURITY);
- objectlist->push_back(objectsignature_t(objectid, gr->gr_name));
- }
- endgrent();
- return objectlist;
- }
- std::unique_ptr<signatures_t>
- UnixUserPlugin::getAllObjects(const objectid_t &companyid,
- objectclass_t objclass)
- {
- ECRESULT er = erSuccess;
- std::unique_ptr<signatures_t> objectlist(new signatures_t());
- std::unique_ptr<signatures_t> objects;
- map<objectclass_t, string> objectstrings;
- DB_RESULT lpResult;
- DB_ROW lpDBRow = NULL;
- string strQuery;
- string strSubQuery;
- unsigned int ulRows = 0;
- if (companyid.id.empty())
- LOG_PLUGIN_DEBUG("%s Class %x", __FUNCTION__, objclass);
- else
- LOG_PLUGIN_DEBUG("%s Company %s, Class %x", __FUNCTION__, companyid.id.c_str(), objclass);
- // use mutex to protect thread-unsafe setpwent()/setgrent() calls
- ulock_normal biglock(m_plugin_lock);
- switch (OBJECTCLASS_TYPE(objclass)) {
- case OBJECTTYPE_UNKNOWN:
- objects = getAllUserObjects();
- objectlist->merge(*objects.get());
- objects = getAllGroupObjects();
- objectlist->merge(*objects.get());
- break;
- case OBJECTTYPE_MAILUSER:
- objects = getAllUserObjects();
- objectlist->merge(*objects.get());
- break;
- case OBJECTTYPE_DISTLIST:
- objects = getAllGroupObjects();
- objectlist->merge(*objects.get());
- break;
- case OBJECTTYPE_CONTAINER:
- throw notsupported("objecttype not supported " + stringify(objclass));
- default:
- throw runtime_error("Unknown object type " + stringify(objclass));
- }
- biglock.unlock();
- // Cleanup old entries from deleted users/groups
- if (objectlist->empty())
- return objectlist;
- // Distribute all objects over the various types
- for (const auto &obj : *objectlist) {
- if (!objectstrings[obj.id.objclass].empty())
- objectstrings[obj.id.objclass] += ", ";
- objectstrings[obj.id.objclass] += obj.id.id;
- }
- // make list of obsolete objects
- strQuery = "SELECT id, objectclass FROM " + (string)DB_OBJECT_TABLE + " WHERE ";
- for (auto iterStrings = objectstrings.cbegin();
- iterStrings != objectstrings.cend(); ++iterStrings) {
- if (iterStrings != objectstrings.cbegin())
- strQuery += "AND ";
- strQuery +=
- "(externid NOT IN (" + iterStrings->second + ") "
- "AND " + OBJECTCLASS_COMPARE_SQL("objectclass", iterStrings->first) + ")";
- }
- er = m_lpDatabase->DoSelect(strQuery, &lpResult);
- if (er != erSuccess) {
- ec_log_err("Unix plugin: Unable to cleanup old entries");
- return objectlist;
- }
- // check if we have obsolute objects
- ulRows = m_lpDatabase->GetNumRows(lpResult);
- if (!ulRows)
- return objectlist;
- // clear our stringlist containing the valid entries and fill it with the deleted item ids
- objectstrings.clear();
- while ((lpDBRow = m_lpDatabase->FetchRow(lpResult)) != NULL) {
- if (!objectstrings[(objectclass_t)atoi(lpDBRow[1])].empty())
- objectstrings[(objectclass_t)atoi(lpDBRow[1])] += ", ";
- objectstrings[(objectclass_t)atoi(lpDBRow[1])] += lpDBRow[0];
- }
- // remove obsolete objects
- strQuery = "DELETE FROM " + (string)DB_OBJECT_TABLE + " WHERE ";
- for (auto iterStrings = objectstrings.cbegin();
- iterStrings != objectstrings.cend(); ++iterStrings) {
- if (iterStrings != objectstrings.cbegin())
- strQuery += "OR ";
- strQuery += "(externid IN (" + iterStrings->second + ") AND objectclass = " + stringify(iterStrings->first) + ")";
- }
- er = m_lpDatabase->DoDelete(strQuery, &ulRows);
- if (er != erSuccess) {
- ec_log_err("Unix plugin: Unable to cleanup old entries in object table");
- return objectlist;
- } else if (ulRows > 0) {
- ec_log_info("Unix plugin: Cleaned up %d old entries from object table", ulRows);
- }
- // Create subquery to select all ids which will be deleted
- strSubQuery =
- "SELECT o.id "
- "FROM " + (string)DB_OBJECT_TABLE + " AS o "
- "WHERE ";
- for (auto iterStrings = objectstrings.cbegin();
- iterStrings != objectstrings.cend(); ++iterStrings) {
- if (iterStrings != objectstrings.cbegin())
- strSubQuery += "OR ";
- strSubQuery += "(o.externid IN (" + iterStrings->second + ") AND o.objectclass = " + stringify(iterStrings->first) + ")";
- }
- // remove obsolute object properties
- strQuery =
- "DELETE FROM " + (string)DB_OBJECTPROPERTY_TABLE + " "
- "WHERE objectid IN (" + strSubQuery + ")";
- er = m_lpDatabase->DoDelete(strQuery, &ulRows);
- if (er != erSuccess) {
- ec_log_err("Unix plugin: Unable to cleanup old entries in objectproperty table");
- return objectlist;
- } else if (ulRows > 0) {
- ec_log_info("Unix plugin: Cleaned up %d old entries from objectproperty table", ulRows);
- }
- strQuery =
- "DELETE FROM " + (string)DB_OBJECT_RELATION_TABLE + " "
- "WHERE objectid IN (" + strSubQuery + ") "
- "OR parentobjectid IN (" + strSubQuery + ")";
- er = m_lpDatabase->DoDelete(strQuery, &ulRows);
- if (er != erSuccess) {
- ec_log_err("Unix plugin: Unable to cleanup old entries in objectrelation table");
- return objectlist;
- } else if (ulRows > 0) {
- ec_log_info("Unix plugin: Cleaned-up %d old entries from objectrelation table", ulRows);
- }
- return objectlist;
- }
- std::unique_ptr<objectdetails_t>
- UnixUserPlugin::getObjectDetails(const objectid_t &externid)
- {
- ECRESULT er = erSuccess;
- char buffer[PWBUFSIZE];
- std::unique_ptr<objectdetails_t> ud;
- struct passwd pws;
- struct group grp;
- DB_RESULT lpResult;
- DB_ROW lpRow = NULL;
- string strQuery;
- LOG_PLUGIN_DEBUG("%s for externid %s, class %d", __FUNCTION__, bin2hex(externid.id).c_str(), externid.objclass);
- switch (externid.objclass) {
- case ACTIVE_USER:
- case NONACTIVE_USER:
- case NONACTIVE_ROOM:
- case NONACTIVE_EQUIPMENT:
- case NONACTIVE_CONTACT:
- findUserID(externid.id, &pws, buffer);
- ud = objectdetailsFromPwent(&pws);
- break;
- case DISTLIST_GROUP:
- case DISTLIST_SECURITY:
- findGroupID(externid.id, &grp, buffer);
- ud = objectdetailsFromGrent(&grp);
- break;
- default:
- throw runtime_error("Object is wrong type");
- break;
- }
- strQuery = "SELECT id FROM " + (string)DB_OBJECT_TABLE + " WHERE externid = '" + externid.id + "' AND objectclass = " + stringify(externid.objclass);
- er = m_lpDatabase->DoSelect(strQuery, &lpResult);
- if (er != erSuccess)
- throw runtime_error(externid.id);
- lpRow = m_lpDatabase->FetchRow(lpResult);
- if (lpRow && lpRow[0]) {
- strQuery = "UPDATE " + (string)DB_OBJECT_TABLE + " SET externid='" + externid.id + "',objectclass=" + stringify(externid.objclass) + " WHERE id=" + lpRow[0];
- er = m_lpDatabase->DoUpdate(strQuery);
- } else {
- strQuery = "INSERT INTO " + (string)DB_OBJECT_TABLE + " (externid, objectclass) VALUES ('" + externid.id + "', " + stringify(externid.objclass) + ")";
- er = m_lpDatabase->DoInsert(strQuery);
- }
- if (er != erSuccess)
- throw runtime_error(externid.id);
- try {
- ud->MergeFrom(*DBPlugin::getObjectDetails(externid));
- } catch (...) { } // ignore errors; we'll try with just the information we have from Pwent
- return ud;
- }
- void UnixUserPlugin::changeObject(const objectid_t &id, const objectdetails_t &details, const std::list<std::string> *lpRemove)
- {
- objectdetails_t tmp(details);
- // explicitly deny pam updates
- if (!details.GetPropString(OB_PROP_S_PASSWORD).empty())
- throw runtime_error("Updating the password is not allowed with the Unix plugin.");
- if (!details.GetPropString(OB_PROP_S_FULLNAME).empty())
- throw runtime_error("Updating the fullname is not allowed with the Unix plugin.");
- // Although updating username is invalid in Unix plugin, we still receive the OB_PROP_S_LOGIN field.
- // This is because kopano-admin -u <username> sends it, and that requirement is because the
- // UpdateUserDetailsFromClient call needs to convert the username/company to details.
- // Remove the username detail to allow updating user information.
- tmp.SetPropString(OB_PROP_S_LOGIN, string());
- DBPlugin::changeObject(id, tmp, lpRemove);
- }
- objectsignature_t UnixUserPlugin::createObject(const objectdetails_t &details) {
- throw notimplemented("Creating objects is not supported when using the Unix user plugin.");
- }
- void UnixUserPlugin::deleteObject(const objectid_t &id) {
- throw notimplemented("Deleting objects is not supported when using the Unix user plugin.");
- }
- void UnixUserPlugin::modifyObjectId(const objectid_t &oldId, const objectid_t &newId)
- {
- throw notimplemented("Modifying objectid is not supported when using the Unix user plugin.");
- }
- std::unique_ptr<signatures_t>
- UnixUserPlugin::getParentObjectsForObject(userobject_relation_t relation,
- const objectid_t &childid)
- {
- std::unique_ptr<signatures_t> objectlist(new signatures_t());
- char buffer[PWBUFSIZE];
- struct passwd pws;
- struct group grs, *gr = NULL;
- gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
- gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
- vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
- set<gid_t> exceptgidset;
- string username;
- if (relation != OBJECTRELATION_GROUP_MEMBER)
- return DBPlugin::getParentObjectsForObject(relation, childid);
- LOG_PLUGIN_DEBUG("%s Relation: Group member", __FUNCTION__);
- findUserID(childid.id, &pws, buffer);
- username = pws.pw_name; // make sure we have a _copy_ of the string, not just another pointer
- try {
- findGroupID(tostring(pws.pw_gid), &grs, buffer);
- objectlist->push_back(objectsignature_t(objectid_t(tostring(grs.gr_gid), DISTLIST_SECURITY), grs.gr_name));
- } catch (std::exception &e) {
- // Ignore error
- }
- transform(exceptgids.begin(), exceptgids.end(), inserter(exceptgidset, exceptgidset.begin()), fromstring<const std::string,gid_t>);
- // This is a rather expensive operation: loop through all the
- // groups, and check each member for each group.
- ulock_normal biglock(m_plugin_lock);
- setgrent();
- while (true) {
- if (getgrent_r(&grs, buffer, PWBUFSIZE, &gr) != 0)
- break;
- if (gr == NULL)
- break;
- // system users don't have kopano accounts
- if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
- continue;
- if (exceptgidset.find(gr->gr_gid) != exceptgidset.end())
- continue;
- for (int i = 0; gr->gr_mem[i] != NULL; ++i)
- if (strcmp(username.c_str(), gr->gr_mem[i]) == 0) {
- objectlist->push_back(objectsignature_t(objectid_t(tostring(gr->gr_gid), DISTLIST_SECURITY), gr->gr_name));
- break;
- }
- }
- endgrent();
- biglock.unlock();
- // because users can be explicitly listed in their default group
- objectlist->sort();
- objectlist->unique();
- return objectlist;
- }
- std::unique_ptr<signatures_t>
- UnixUserPlugin::getSubObjectsForObject(userobject_relation_t relation,
- const objectid_t &parentid)
- {
- std::unique_ptr<signatures_t> objectlist(new signatures_t());
- char buffer[PWBUFSIZE];
- struct passwd pws, *pw = NULL;
- struct group grp;
- uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
- uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
- const char *lpszNonActive = m_config->GetSetting("non_login_shell");
- gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
- gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
- vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
- set<uid_t> exceptuidset;
- objectid_t objectid;
- if (relation != OBJECTRELATION_GROUP_MEMBER)
- return DBPlugin::getSubObjectsForObject(relation, parentid);
- LOG_PLUGIN_DEBUG("%s Relation: Group member", __FUNCTION__);
- findGroupID(parentid.id, &grp, buffer);
- for (unsigned int i = 0; grp.gr_mem[i] != NULL; ++i)
- try {
- objectlist->push_back(resolveUserName(grp.gr_mem[i]));
- } catch (std::exception &e) {
- // Ignore error
- }
- transform(exceptuids.begin(), exceptuids.end(), inserter(exceptuidset, exceptuidset.begin()), fromstring<const std::string,uid_t>);
- // iterate through /etc/passwd users to find default group (eg. users) and add it to the list
- ulock_normal biglock(m_plugin_lock);
- setpwent();
- while (true) {
- if (getpwent_r(&pws, buffer, PWBUFSIZE, &pw) != 0)
- break;
- if (pw == NULL)
- break;
- // system users don't have kopano accounts
- if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
- continue;
- if (exceptuidset.find(pw->pw_uid) != exceptuidset.end())
- continue;
- // is it a member, and fits the default group in the range?
- if (pw->pw_gid == grp.gr_gid && pw->pw_gid >= mingid && pw->pw_gid < maxgid) {
- if (strcmp(pw->pw_shell, lpszNonActive) != 0)
- objectid = objectid_t(tostring(pw->pw_uid), ACTIVE_USER);
- else
- objectid = objectid_t(tostring(pw->pw_uid), NONACTIVE_USER);
- objectlist->push_back(objectsignature_t(objectid, getDBSignature(objectid) + pw->pw_gecos + pw->pw_name));
- }
- }
- endpwent();
- biglock.unlock();
- // because users can be explicitly listed in their default group
- objectlist->sort();
- objectlist->unique();
- return objectlist;
- }
- void UnixUserPlugin::addSubObjectRelation(userobject_relation_t relation, const objectid_t &id, const objectid_t &member)
- {
- if (relation != OBJECTRELATION_QUOTA_USERRECIPIENT && relation != OBJECTRELATION_USER_SENDAS)
- throw notimplemented("Adding object relations is not supported when using the Unix user plugin.");
- DBPlugin::addSubObjectRelation(relation, id, member);
- }
- void UnixUserPlugin::deleteSubObjectRelation(userobject_relation_t relation, const objectid_t &id, const objectid_t &member)
- {
- if (relation != OBJECTRELATION_QUOTA_USERRECIPIENT && relation != OBJECTRELATION_USER_SENDAS)
- throw notimplemented("Deleting object relations is not supported when using the Unix user plugin.");
- DBPlugin::deleteSubObjectRelation(relation, id, member);
- }
- std::unique_ptr<signatures_t>
- UnixUserPlugin::searchObject(const std::string &match, unsigned int ulFlags)
- {
- char buffer[PWBUFSIZE];
- struct passwd pws, *pw = NULL;
- std::unique_ptr<signatures_t> objectlist(new signatures_t());
- std::unique_ptr<signatures_t> objects;
- LOG_PLUGIN_DEBUG("%s %s flags:%x", __FUNCTION__, match.c_str(), ulFlags);
- ulock_normal biglock(m_plugin_lock);
- objects = getAllUserObjects(match, ulFlags);
- objectlist->merge(*objects.get());
- objects = getAllGroupObjects(match, ulFlags);
- objectlist->merge(*objects.get());
- biglock.unlock();
- // See if we get matches based on database details as well
- try {
- const char *search_props[] = { OP_EMAILADDRESS, NULL };
- objects = DBPlugin::searchObjects(match, search_props, NULL, ulFlags);
- for (const auto &sig : *objects) {
- // the DBPlugin returned the DB signature, so we need to prepend this with the gecos signature
- int ret = getpwuid_r(atoi(sig.id.id.c_str()), &pws, buffer, PWBUFSIZE, &pw);
- if (ret != 0)
- errnoCheck(sig.id.id, ret);
- if (pw == NULL) // object not found anymore
- continue;
- objectlist->push_back(objectsignature_t(sig.id, sig.signature + pw->pw_gecos + pw->pw_name));
- }
- } catch (objectnotfound &e) {
- // Ignore exception, we will check lObjects.empty() later.
- } // All other exceptions should be thrown further up the chain.
- objectlist->sort();
- objectlist->unique();
- if (objectlist->empty())
- throw objectnotfound(string("unix_plugin: no match: ") + match);
- return objectlist;
- }
- std::unique_ptr<objectdetails_t> UnixUserPlugin::getPublicStoreDetails(void)
- {
- throw notsupported("public store details");
- }
- std::unique_ptr<serverdetails_t>
- UnixUserPlugin::getServerDetails(const std::string &server)
- {
- throw notsupported("server details");
- }
- std::unique_ptr<serverlist_t> UnixUserPlugin::getServers(void)
- {
- throw notsupported("server list");
- }
- std::unique_ptr<std::map<objectid_t, objectdetails_t> >
- UnixUserPlugin::getObjectDetails(const std::list<objectid_t> &objectids)
- {
- std::unique_ptr<std::map<objectid_t, objectdetails_t> > mapdetails(new map<objectid_t, objectdetails_t>());
- std::unique_ptr<objectdetails_t> uDetails;
- objectdetails_t details;
- if (objectids.empty())
- return mapdetails;
- for (const auto &id : objectids) {
- try {
- uDetails = this->getObjectDetails(id);
- }
- catch (objectnotfound &e) {
- // ignore not found error
- continue;
- }
- (*mapdetails)[id] = (*uDetails.get());
- }
-
- return mapdetails;
- }
- // -------------
- // private
- // -------------
- std::unique_ptr<objectdetails_t>
- UnixUserPlugin::objectdetailsFromPwent(struct passwd *pw)
- {
- std::unique_ptr<objectdetails_t> ud(new objectdetails_t());
- string gecos;
- size_t comma;
- ud->SetPropString(OB_PROP_S_LOGIN, string(pw->pw_name));
- if (strcmp(pw->pw_shell, m_config->GetSetting("non_login_shell")) == 0)
- ud->SetClass(NONACTIVE_USER);
- else
- ud->SetClass(ACTIVE_USER);
- gecos = m_iconv->convert(pw->pw_gecos);
- // gecos may contain room/phone number etc. too
- comma = gecos.find(",");
- if (comma != string::npos) {
- ud->SetPropString(OB_PROP_S_FULLNAME, gecos.substr(0,comma));
- } else {
- ud->SetPropString(OB_PROP_S_FULLNAME, gecos);
- }
- if (!strcmp(pw->pw_passwd, "x")) {
- // shadow password entry
- struct spwd spws, *spw = NULL;
- char sbuffer[PWBUFSIZE];
- if (getspnam_r(pw->pw_name, &spws, sbuffer, PWBUFSIZE, &spw) != 0) {
- ec_log_warn("getspname_r: %s", strerror(errno));
- /* set invalid password entry, cannot login without a password */
- ud->SetPropString(OB_PROP_S_PASSWORD, std::string("x"));
- } else if (spw == NULL) {
- // invalid entry, must have a shadow password set in this case
- // throw objectnotfound(ud->id);
- // too bad that the password couldn't be found, but it's not that critical
- ec_log_warn("Warning: unable to find password for user \"%s\": %s", pw->pw_name, strerror(errno));
- ud->SetPropString(OB_PROP_S_PASSWORD, string("x")); // set invalid password entry, cannot login without a password
- } else {
- ud->SetPropString(OB_PROP_S_PASSWORD, string(spw->sp_pwdp));
- }
- } else if (!strcmp(pw->pw_passwd, "*") || !strcmp(pw->pw_passwd, "!")){
- throw objectnotfound(string());
- } else {
- ud->SetPropString(OB_PROP_S_PASSWORD, string(pw->pw_passwd));
- }
-
- // This may be overridden by settings in the database
- ud->SetPropString(OB_PROP_S_EMAIL, string(pw->pw_name) + "@" + m_config->GetSetting("default_domain"));
- return ud;
- }
- std::unique_ptr<objectdetails_t>
- UnixUserPlugin::objectdetailsFromGrent(struct group *gr)
- {
- std::unique_ptr<objectdetails_t> gd(new objectdetails_t(DISTLIST_SECURITY));
- gd->SetPropString(OB_PROP_S_LOGIN, string(gr->gr_name));
- gd->SetPropString(OB_PROP_S_FULLNAME, string(gr->gr_name));
- return gd;
- }
- std::string UnixUserPlugin::getDBSignature(const objectid_t &id)
- {
- string strQuery;
- DB_RESULT lpResult;
- DB_ROW lpDBRow = NULL;
- ECRESULT er = erSuccess;
- strQuery =
- "SELECT op.value "
- "FROM " + (string)DB_OBJECTPROPERTY_TABLE + " AS op "
- "JOIN " + (string)DB_OBJECT_TABLE + " AS o "
- "ON op.objectid = o.id "
- "WHERE o.externid = '" + m_lpDatabase->Escape(id.id) + "' "
- "AND o.objectclass = " + stringify(id.objclass) + " "
- "AND op.propname = '" + OP_MODTIME + "'";
- er = m_lpDatabase->DoSelect(strQuery, &lpResult);
- if (er != erSuccess)
- return string();
- lpDBRow = m_lpDatabase->FetchRow(lpResult);
- if (lpDBRow == NULL || lpDBRow[0] == NULL)
- return string();
- return lpDBRow[0];
- }
- void UnixUserPlugin::errnoCheck(const std::string &user, int e) const
- {
- if (e != 0) {
- char buffer[256];
- char *retbuf;
- retbuf = strerror_r(e, buffer, 256);
- // from the getpwnam() man page: (notice the last or...)
- // ERRORS
- // 0 or ENOENT or ESRCH or EBADF or EPERM or ...
- // The given name or uid was not found.
- switch (e) {
- // 0 is handled in top if()
- case ENOENT:
- case ESRCH:
- case EBADF:
- case EPERM:
- // calling function must check pw == NULL to throw objectnotfound()
- break;
- default:
- // broken system .. do not delete user from database
- throw runtime_error(string("unable to query for user ")+user+string(". Error: ")+retbuf);
- };
- }
- }
|