UnixUserPlugin.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/platform.h>
  18. #include <exception>
  19. #include <iostream>
  20. #include <mutex>
  21. #include <string>
  22. #include <stdexcept>
  23. #include <sys/types.h>
  24. #include <pwd.h>
  25. #include <sstream>
  26. #include <cassert>
  27. #include <sys/stat.h>
  28. #include <grp.h>
  29. #include <crypt.h>
  30. #include <set>
  31. #include <iterator>
  32. #ifdef HAVE_SHADOW_H
  33. #include <shadow.h>
  34. #include <cerrno>
  35. #endif
  36. #include <kopano/EMSAbTag.h>
  37. #include <kopano/ECConfig.h>
  38. #include <kopano/ECDefs.h>
  39. #include <kopano/ECLogger.h>
  40. #include <kopano/ECPluginSharedData.h>
  41. #include <kopano/lockhelper.hpp>
  42. #include <kopano/stringutil.h>
  43. using namespace std;
  44. #include "UnixUserPlugin.h"
  45. #include <kopano/ecversion.h>
  46. /**
  47. * static buffer size for getpwnam_r() calls etc.
  48. * needs to be big enough for all strings in the passwd/group/spwd struct:
  49. */
  50. #define PWBUFSIZE 16384
  51. extern "C" {
  52. UserPlugin *getUserPluginInstance(std::mutex &pluginlock,
  53. ECPluginSharedData *shareddata)
  54. {
  55. return new UnixUserPlugin(pluginlock, shareddata);
  56. }
  57. void deleteUserPluginInstance(UserPlugin *up) {
  58. delete up;
  59. }
  60. int getUserPluginVersion() {
  61. return PROJECT_VERSION_REVISION;
  62. }
  63. }
  64. UnixUserPlugin::UnixUserPlugin(std::mutex &pluginlock,
  65. ECPluginSharedData *shareddata) :
  66. DBPlugin(pluginlock, shareddata)
  67. {
  68. const configsetting_t lpDefaults [] = {
  69. { "fullname_charset", "iso-8859-15" }, // US-ASCII compatible with support for high characters
  70. { "default_domain", "localhost" }, // no sane default
  71. { "non_login_shell", "/bin/false", CONFIGSETTING_RELOADABLE }, // create a non-login box when a user has this shell
  72. { "min_user_uid", "1000", CONFIGSETTING_RELOADABLE },
  73. { "max_user_uid", "10000", CONFIGSETTING_RELOADABLE },
  74. { "except_user_uids", "", CONFIGSETTING_RELOADABLE },
  75. { "min_group_gid", "1000", CONFIGSETTING_RELOADABLE },
  76. { "max_group_gid", "10000", CONFIGSETTING_RELOADABLE },
  77. { "except_group_gids", "", CONFIGSETTING_RELOADABLE },
  78. { NULL, NULL }
  79. };
  80. m_config = shareddata->CreateConfig(lpDefaults);
  81. if (!m_config)
  82. throw runtime_error(string("Not a valid configuration file."));
  83. if (m_bHosted)
  84. throw notsupported("Hosted Kopano not supported when using the Unix Plugin");
  85. if (m_bDistributed)
  86. throw notsupported("Distributed Kopano not supported when using the Unix Plugin");
  87. }
  88. UnixUserPlugin::~UnixUserPlugin()
  89. {
  90. delete m_iconv;
  91. }
  92. void UnixUserPlugin::InitPlugin() {
  93. DBPlugin::InitPlugin();
  94. // we only need unix_charset -> kopano charset
  95. m_iconv = new ECIConv("utf-8", m_config->GetSetting("fullname_charset"));
  96. if (!m_iconv -> canConvert())
  97. throw runtime_error(string("Cannot setup charset converter, check \"fullname_charset\" in cfg"));
  98. }
  99. void UnixUserPlugin::findUserID(const string &id, struct passwd *pwd, char *buffer)
  100. {
  101. struct passwd *pw = NULL;
  102. uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
  103. uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
  104. vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
  105. objectid_t objectid;
  106. int ret = getpwuid_r(atoi(id.c_str()), pwd, buffer, PWBUFSIZE, &pw);
  107. if (ret != 0)
  108. errnoCheck(id, ret);
  109. if (pw == NULL)
  110. throw objectnotfound(id);
  111. if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
  112. throw objectnotfound(id);
  113. for (unsigned int i = 0; i < exceptuids.size(); ++i)
  114. if (pw->pw_uid == fromstring<const std::string, uid_t>(exceptuids[i]))
  115. throw objectnotfound(id);
  116. }
  117. void UnixUserPlugin::findUser(const string &name, struct passwd *pwd, char *buffer)
  118. {
  119. struct passwd *pw = NULL;
  120. uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
  121. uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
  122. vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
  123. objectid_t objectid;
  124. int ret = getpwnam_r(name.c_str(), pwd, buffer, PWBUFSIZE, &pw);
  125. if (ret != 0)
  126. errnoCheck(name, ret);
  127. if (pw == NULL)
  128. throw objectnotfound(name);
  129. if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
  130. throw objectnotfound(name);
  131. for (unsigned int i = 0; i < exceptuids.size(); ++i)
  132. if (pw->pw_uid == fromstring<const std::string, uid_t>(exceptuids[i]))
  133. throw objectnotfound(name);
  134. }
  135. void UnixUserPlugin::findGroupID(const string &id, struct group *grp, char *buffer)
  136. {
  137. struct group *gr = NULL;
  138. gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
  139. gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
  140. vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
  141. objectid_t objectid;
  142. int ret = getgrgid_r(atoi(id.c_str()), grp, buffer, PWBUFSIZE, &gr);
  143. if (ret != 0)
  144. errnoCheck(id, ret);
  145. if (gr == NULL)
  146. throw objectnotfound(id);
  147. if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
  148. throw objectnotfound(id);
  149. for (unsigned int i = 0; i < exceptgids.size(); ++i)
  150. if (gr->gr_gid == fromstring<const std::string, gid_t>(exceptgids[i]))
  151. throw objectnotfound(id);
  152. }
  153. void UnixUserPlugin::findGroup(const string &name, struct group *grp, char *buffer)
  154. {
  155. struct group *gr = NULL;
  156. gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
  157. gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
  158. vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
  159. objectid_t objectid;
  160. int ret = getgrnam_r(name.c_str(), grp, buffer, PWBUFSIZE, &gr);
  161. if (ret != 0)
  162. errnoCheck(name, ret);
  163. if (gr == NULL)
  164. throw objectnotfound(name);
  165. if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
  166. throw objectnotfound(name);
  167. for (unsigned int i = 0; i < exceptgids.size(); ++i)
  168. if (gr->gr_gid == fromstring<const std::string, gid_t>(exceptgids[i]))
  169. throw objectnotfound(name);
  170. }
  171. objectsignature_t UnixUserPlugin::resolveUserName(const string &name)
  172. {
  173. char buffer[PWBUFSIZE];
  174. struct passwd pws;
  175. const char *lpszNonActive = m_config->GetSetting("non_login_shell");
  176. objectid_t objectid;
  177. findUser(name, &pws, buffer);
  178. if (strcmp(pws.pw_shell, lpszNonActive) != 0)
  179. objectid = objectid_t(tostring(pws.pw_uid), ACTIVE_USER);
  180. else
  181. objectid = objectid_t(tostring(pws.pw_uid), NONACTIVE_USER);
  182. return objectsignature_t(objectid, getDBSignature(objectid) + pws.pw_gecos + pws.pw_name);
  183. }
  184. objectsignature_t UnixUserPlugin::resolveGroupName(const string &name)
  185. {
  186. char buffer[PWBUFSIZE];
  187. struct group grp;
  188. objectid_t objectid;
  189. findGroup(name, &grp, buffer);
  190. objectid = objectid_t(tostring(grp.gr_gid), DISTLIST_SECURITY);
  191. return objectsignature_t(objectid, grp.gr_name);
  192. }
  193. objectsignature_t UnixUserPlugin::resolveName(objectclass_t objclass, const string &name, const objectid_t &company)
  194. {
  195. objectsignature_t user;
  196. objectsignature_t group;
  197. if (company.id.empty()) {
  198. LOG_PLUGIN_DEBUG("%s Class %x, Name %s", __FUNCTION__, objclass, name.c_str());
  199. } else {
  200. LOG_PLUGIN_DEBUG("%s Class %x, Name %s, Company %s", __FUNCTION__, objclass, name.c_str(), company.id.c_str());
  201. }
  202. switch (OBJECTCLASS_TYPE(objclass)) {
  203. case OBJECTTYPE_UNKNOWN:
  204. // Caller doesn't know what he is looking for, try searching through
  205. // users and groups. Note that 1 function _must_ fail because otherwise
  206. // we have a duplicate entry.
  207. try {
  208. user = resolveUserName(name);
  209. } catch (std::exception &e) {
  210. // object is not a user
  211. }
  212. try {
  213. group = resolveGroupName(name);
  214. } catch (std::exception &e) {
  215. // object is not a group
  216. }
  217. if (!user.id.id.empty()) {
  218. // Object is both user and group
  219. if (!group.id.id.empty())
  220. throw toomanyobjects(name);
  221. return user;
  222. } else {
  223. // Object is neither user not group
  224. if (group.id.id.empty())
  225. throw objectnotfound(name);
  226. return group;
  227. }
  228. case OBJECTTYPE_MAILUSER:
  229. return resolveUserName(name);
  230. case OBJECTTYPE_DISTLIST:
  231. return resolveGroupName(name);
  232. default:
  233. throw runtime_error("Unknown object type " + stringify(objclass));
  234. }
  235. }
  236. objectsignature_t UnixUserPlugin::authenticateUser(const string &username, const string &password, const objectid_t &companyname) {
  237. struct passwd pws, *pw = NULL;
  238. char buffer[PWBUFSIZE];
  239. uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
  240. uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
  241. vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
  242. std::unique_ptr<struct crypt_data> cryptdata;
  243. std::unique_ptr<objectdetails_t> ud;
  244. objectid_t objectid;
  245. const char *crpw = NULL;
  246. cryptdata.reset(new struct crypt_data); // malloc because it is > 128K !
  247. memset(cryptdata.get(), 0, sizeof(struct crypt_data));
  248. int ret = getpwnam_r(username.c_str(), &pws, buffer, PWBUFSIZE, &pw);
  249. if (ret != 0)
  250. errnoCheck(username, ret);
  251. if (pw == NULL)
  252. throw objectnotfound(username);
  253. if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
  254. throw objectnotfound(username);
  255. for (unsigned i = 0; i < exceptuids.size(); ++i)
  256. if (pw->pw_uid == fromstring<const std::string, uid_t>(exceptuids[i]))
  257. throw objectnotfound(username);
  258. if (strcmp(pw->pw_shell, m_config->GetSetting("non_login_shell")) == 0)
  259. throw login_error("Non-active user disallowed to login");
  260. ud = objectdetailsFromPwent(pw);
  261. crpw = crypt_r(password.c_str(), ud->GetPropString(OB_PROP_S_PASSWORD).c_str(), cryptdata.get());
  262. if (!crpw || strcmp(crpw, ud->GetPropString(OB_PROP_S_PASSWORD).c_str()))
  263. throw login_error("Trying to authenticate failed: wrong username or password");
  264. objectid = objectid_t(tostring(pw->pw_uid), ACTIVE_USER);
  265. return objectsignature_t(objectid, getDBSignature(objectid) + pw->pw_gecos + pw->pw_name);
  266. }
  267. bool UnixUserPlugin::matchUserObject(struct passwd *pw, const string &match, unsigned int ulFlags)
  268. {
  269. string email;
  270. bool matched = false;
  271. // username or fullname
  272. if(ulFlags & EMS_AB_ADDRESS_LOOKUP) {
  273. matched =
  274. (strcasecmp(pw->pw_name, (char*)match.c_str()) == 0) ||
  275. (strcasecmp((char*)m_iconv->convert(pw->pw_gecos).c_str(), (char*)match.c_str()) == 0);
  276. } else {
  277. matched =
  278. (strncasecmp(pw->pw_name, (char*)match.c_str(), match.size()) == 0) ||
  279. (strncasecmp((char*)m_iconv->convert(pw->pw_gecos).c_str(), (char*)match.c_str(), match.size()) == 0);
  280. }
  281. if (matched)
  282. return matched;
  283. email = string(pw->pw_name) + "@" + m_config->GetSetting("default_domain");
  284. if(ulFlags & EMS_AB_ADDRESS_LOOKUP)
  285. matched = (email == match);
  286. else
  287. matched = (strncasecmp((char*)email.c_str(), (char*)match.c_str(), match.size()) == 0);
  288. return matched;
  289. }
  290. bool UnixUserPlugin::matchGroupObject(struct group *gr, const string &match, unsigned int ulFlags)
  291. {
  292. bool matched = false;
  293. if(ulFlags & EMS_AB_ADDRESS_LOOKUP)
  294. matched = strcasecmp(gr->gr_name, (char*)match.c_str()) == 0;
  295. else
  296. matched = strncasecmp(gr->gr_name, (char*)match.c_str(), match.size()) == 0;
  297. return matched;
  298. }
  299. std::unique_ptr<signatures_t>
  300. UnixUserPlugin::getAllUserObjects(const std::string &match,
  301. unsigned int ulFlags)
  302. {
  303. std::unique_ptr<signatures_t> objectlist(new signatures_t());
  304. char buffer[PWBUFSIZE];
  305. struct passwd pws, *pw = NULL;
  306. uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
  307. uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
  308. const char *lpszNonActive = m_config->GetSetting("non_login_shell");
  309. vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
  310. set<uid_t> exceptuidset;
  311. objectid_t objectid;
  312. transform(exceptuids.begin(), exceptuids.end(), inserter(exceptuidset, exceptuidset.begin()), fromstring<const std::string,uid_t>);
  313. setpwent();
  314. while (true) {
  315. if (getpwent_r(&pws, buffer, PWBUFSIZE, &pw) != 0)
  316. break;
  317. if (pw == NULL)
  318. break;
  319. // system users don't have kopano accounts
  320. if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
  321. continue;
  322. if (exceptuidset.find(pw->pw_uid) != exceptuidset.end())
  323. continue;
  324. if (!match.empty() && !matchUserObject(pw, match, ulFlags))
  325. continue;
  326. if (strcmp(pw->pw_shell, lpszNonActive) != 0)
  327. objectid = objectid_t(tostring(pw->pw_uid), ACTIVE_USER);
  328. else
  329. objectid = objectid_t(tostring(pw->pw_uid), NONACTIVE_USER);
  330. objectlist->push_back(objectsignature_t(objectid, getDBSignature(objectid) + pw->pw_gecos + pw->pw_name));
  331. }
  332. endpwent();
  333. return objectlist;
  334. }
  335. std::unique_ptr<signatures_t>
  336. UnixUserPlugin::getAllGroupObjects(const std::string &match,
  337. unsigned int ulFlags)
  338. {
  339. std::unique_ptr<signatures_t> objectlist(new signatures_t());
  340. char buffer[PWBUFSIZE];
  341. struct group grs, *gr = NULL;
  342. gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
  343. gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
  344. vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
  345. set<gid_t> exceptgidset;
  346. objectid_t objectid;
  347. transform(exceptgids.begin(), exceptgids.end(), inserter(exceptgidset, exceptgidset.begin()), fromstring<const std::string,uid_t>);
  348. setgrent();
  349. while (true) {
  350. if (getgrent_r(&grs, buffer, PWBUFSIZE, &gr) != 0)
  351. break;
  352. if (gr == NULL)
  353. break;
  354. // system groups don't have kopano accounts
  355. if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
  356. continue;
  357. if (exceptgidset.find(gr->gr_gid) != exceptgidset.end())
  358. continue;
  359. if (!match.empty() && !matchGroupObject(gr, match, ulFlags))
  360. continue;
  361. objectid = objectid_t(tostring(gr->gr_gid), DISTLIST_SECURITY);
  362. objectlist->push_back(objectsignature_t(objectid, gr->gr_name));
  363. }
  364. endgrent();
  365. return objectlist;
  366. }
  367. std::unique_ptr<signatures_t>
  368. UnixUserPlugin::getAllObjects(const objectid_t &companyid,
  369. objectclass_t objclass)
  370. {
  371. ECRESULT er = erSuccess;
  372. std::unique_ptr<signatures_t> objectlist(new signatures_t());
  373. std::unique_ptr<signatures_t> objects;
  374. map<objectclass_t, string> objectstrings;
  375. DB_RESULT lpResult;
  376. DB_ROW lpDBRow = NULL;
  377. string strQuery;
  378. string strSubQuery;
  379. unsigned int ulRows = 0;
  380. if (companyid.id.empty())
  381. LOG_PLUGIN_DEBUG("%s Class %x", __FUNCTION__, objclass);
  382. else
  383. LOG_PLUGIN_DEBUG("%s Company %s, Class %x", __FUNCTION__, companyid.id.c_str(), objclass);
  384. // use mutex to protect thread-unsafe setpwent()/setgrent() calls
  385. ulock_normal biglock(m_plugin_lock);
  386. switch (OBJECTCLASS_TYPE(objclass)) {
  387. case OBJECTTYPE_UNKNOWN:
  388. objects = getAllUserObjects();
  389. objectlist->merge(*objects.get());
  390. objects = getAllGroupObjects();
  391. objectlist->merge(*objects.get());
  392. break;
  393. case OBJECTTYPE_MAILUSER:
  394. objects = getAllUserObjects();
  395. objectlist->merge(*objects.get());
  396. break;
  397. case OBJECTTYPE_DISTLIST:
  398. objects = getAllGroupObjects();
  399. objectlist->merge(*objects.get());
  400. break;
  401. case OBJECTTYPE_CONTAINER:
  402. throw notsupported("objecttype not supported " + stringify(objclass));
  403. default:
  404. throw runtime_error("Unknown object type " + stringify(objclass));
  405. }
  406. biglock.unlock();
  407. // Cleanup old entries from deleted users/groups
  408. if (objectlist->empty())
  409. return objectlist;
  410. // Distribute all objects over the various types
  411. for (const auto &obj : *objectlist) {
  412. if (!objectstrings[obj.id.objclass].empty())
  413. objectstrings[obj.id.objclass] += ", ";
  414. objectstrings[obj.id.objclass] += obj.id.id;
  415. }
  416. // make list of obsolete objects
  417. strQuery = "SELECT id, objectclass FROM " + (string)DB_OBJECT_TABLE + " WHERE ";
  418. for (auto iterStrings = objectstrings.cbegin();
  419. iterStrings != objectstrings.cend(); ++iterStrings) {
  420. if (iterStrings != objectstrings.cbegin())
  421. strQuery += "AND ";
  422. strQuery +=
  423. "(externid NOT IN (" + iterStrings->second + ") "
  424. "AND " + OBJECTCLASS_COMPARE_SQL("objectclass", iterStrings->first) + ")";
  425. }
  426. er = m_lpDatabase->DoSelect(strQuery, &lpResult);
  427. if (er != erSuccess) {
  428. ec_log_err("Unix plugin: Unable to cleanup old entries");
  429. return objectlist;
  430. }
  431. // check if we have obsolute objects
  432. ulRows = m_lpDatabase->GetNumRows(lpResult);
  433. if (!ulRows)
  434. return objectlist;
  435. // clear our stringlist containing the valid entries and fill it with the deleted item ids
  436. objectstrings.clear();
  437. while ((lpDBRow = m_lpDatabase->FetchRow(lpResult)) != NULL) {
  438. if (!objectstrings[(objectclass_t)atoi(lpDBRow[1])].empty())
  439. objectstrings[(objectclass_t)atoi(lpDBRow[1])] += ", ";
  440. objectstrings[(objectclass_t)atoi(lpDBRow[1])] += lpDBRow[0];
  441. }
  442. // remove obsolete objects
  443. strQuery = "DELETE FROM " + (string)DB_OBJECT_TABLE + " WHERE ";
  444. for (auto iterStrings = objectstrings.cbegin();
  445. iterStrings != objectstrings.cend(); ++iterStrings) {
  446. if (iterStrings != objectstrings.cbegin())
  447. strQuery += "OR ";
  448. strQuery += "(externid IN (" + iterStrings->second + ") AND objectclass = " + stringify(iterStrings->first) + ")";
  449. }
  450. er = m_lpDatabase->DoDelete(strQuery, &ulRows);
  451. if (er != erSuccess) {
  452. ec_log_err("Unix plugin: Unable to cleanup old entries in object table");
  453. return objectlist;
  454. } else if (ulRows > 0) {
  455. ec_log_info("Unix plugin: Cleaned up %d old entries from object table", ulRows);
  456. }
  457. // Create subquery to select all ids which will be deleted
  458. strSubQuery =
  459. "SELECT o.id "
  460. "FROM " + (string)DB_OBJECT_TABLE + " AS o "
  461. "WHERE ";
  462. for (auto iterStrings = objectstrings.cbegin();
  463. iterStrings != objectstrings.cend(); ++iterStrings) {
  464. if (iterStrings != objectstrings.cbegin())
  465. strSubQuery += "OR ";
  466. strSubQuery += "(o.externid IN (" + iterStrings->second + ") AND o.objectclass = " + stringify(iterStrings->first) + ")";
  467. }
  468. // remove obsolute object properties
  469. strQuery =
  470. "DELETE FROM " + (string)DB_OBJECTPROPERTY_TABLE + " "
  471. "WHERE objectid IN (" + strSubQuery + ")";
  472. er = m_lpDatabase->DoDelete(strQuery, &ulRows);
  473. if (er != erSuccess) {
  474. ec_log_err("Unix plugin: Unable to cleanup old entries in objectproperty table");
  475. return objectlist;
  476. } else if (ulRows > 0) {
  477. ec_log_info("Unix plugin: Cleaned up %d old entries from objectproperty table", ulRows);
  478. }
  479. strQuery =
  480. "DELETE FROM " + (string)DB_OBJECT_RELATION_TABLE + " "
  481. "WHERE objectid IN (" + strSubQuery + ") "
  482. "OR parentobjectid IN (" + strSubQuery + ")";
  483. er = m_lpDatabase->DoDelete(strQuery, &ulRows);
  484. if (er != erSuccess) {
  485. ec_log_err("Unix plugin: Unable to cleanup old entries in objectrelation table");
  486. return objectlist;
  487. } else if (ulRows > 0) {
  488. ec_log_info("Unix plugin: Cleaned-up %d old entries from objectrelation table", ulRows);
  489. }
  490. return objectlist;
  491. }
  492. std::unique_ptr<objectdetails_t>
  493. UnixUserPlugin::getObjectDetails(const objectid_t &externid)
  494. {
  495. ECRESULT er = erSuccess;
  496. char buffer[PWBUFSIZE];
  497. std::unique_ptr<objectdetails_t> ud;
  498. struct passwd pws;
  499. struct group grp;
  500. DB_RESULT lpResult;
  501. DB_ROW lpRow = NULL;
  502. string strQuery;
  503. LOG_PLUGIN_DEBUG("%s for externid %s, class %d", __FUNCTION__, bin2hex(externid.id).c_str(), externid.objclass);
  504. switch (externid.objclass) {
  505. case ACTIVE_USER:
  506. case NONACTIVE_USER:
  507. case NONACTIVE_ROOM:
  508. case NONACTIVE_EQUIPMENT:
  509. case NONACTIVE_CONTACT:
  510. findUserID(externid.id, &pws, buffer);
  511. ud = objectdetailsFromPwent(&pws);
  512. break;
  513. case DISTLIST_GROUP:
  514. case DISTLIST_SECURITY:
  515. findGroupID(externid.id, &grp, buffer);
  516. ud = objectdetailsFromGrent(&grp);
  517. break;
  518. default:
  519. throw runtime_error("Object is wrong type");
  520. break;
  521. }
  522. strQuery = "SELECT id FROM " + (string)DB_OBJECT_TABLE + " WHERE externid = '" + externid.id + "' AND objectclass = " + stringify(externid.objclass);
  523. er = m_lpDatabase->DoSelect(strQuery, &lpResult);
  524. if (er != erSuccess)
  525. throw runtime_error(externid.id);
  526. lpRow = m_lpDatabase->FetchRow(lpResult);
  527. if (lpRow && lpRow[0]) {
  528. strQuery = "UPDATE " + (string)DB_OBJECT_TABLE + " SET externid='" + externid.id + "',objectclass=" + stringify(externid.objclass) + " WHERE id=" + lpRow[0];
  529. er = m_lpDatabase->DoUpdate(strQuery);
  530. } else {
  531. strQuery = "INSERT INTO " + (string)DB_OBJECT_TABLE + " (externid, objectclass) VALUES ('" + externid.id + "', " + stringify(externid.objclass) + ")";
  532. er = m_lpDatabase->DoInsert(strQuery);
  533. }
  534. if (er != erSuccess)
  535. throw runtime_error(externid.id);
  536. try {
  537. ud->MergeFrom(*DBPlugin::getObjectDetails(externid));
  538. } catch (...) { } // ignore errors; we'll try with just the information we have from Pwent
  539. return ud;
  540. }
  541. void UnixUserPlugin::changeObject(const objectid_t &id, const objectdetails_t &details, const std::list<std::string> *lpRemove)
  542. {
  543. objectdetails_t tmp(details);
  544. // explicitly deny pam updates
  545. if (!details.GetPropString(OB_PROP_S_PASSWORD).empty())
  546. throw runtime_error("Updating the password is not allowed with the Unix plugin.");
  547. if (!details.GetPropString(OB_PROP_S_FULLNAME).empty())
  548. throw runtime_error("Updating the fullname is not allowed with the Unix plugin.");
  549. // Although updating username is invalid in Unix plugin, we still receive the OB_PROP_S_LOGIN field.
  550. // This is because kopano-admin -u <username> sends it, and that requirement is because the
  551. // UpdateUserDetailsFromClient call needs to convert the username/company to details.
  552. // Remove the username detail to allow updating user information.
  553. tmp.SetPropString(OB_PROP_S_LOGIN, string());
  554. DBPlugin::changeObject(id, tmp, lpRemove);
  555. }
  556. objectsignature_t UnixUserPlugin::createObject(const objectdetails_t &details) {
  557. throw notimplemented("Creating objects is not supported when using the Unix user plugin.");
  558. }
  559. void UnixUserPlugin::deleteObject(const objectid_t &id) {
  560. throw notimplemented("Deleting objects is not supported when using the Unix user plugin.");
  561. }
  562. void UnixUserPlugin::modifyObjectId(const objectid_t &oldId, const objectid_t &newId)
  563. {
  564. throw notimplemented("Modifying objectid is not supported when using the Unix user plugin.");
  565. }
  566. std::unique_ptr<signatures_t>
  567. UnixUserPlugin::getParentObjectsForObject(userobject_relation_t relation,
  568. const objectid_t &childid)
  569. {
  570. std::unique_ptr<signatures_t> objectlist(new signatures_t());
  571. char buffer[PWBUFSIZE];
  572. struct passwd pws;
  573. struct group grs, *gr = NULL;
  574. gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
  575. gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
  576. vector<string> exceptgids = tokenize(m_config->GetSetting("except_group_gids"), " \t");
  577. set<gid_t> exceptgidset;
  578. string username;
  579. if (relation != OBJECTRELATION_GROUP_MEMBER)
  580. return DBPlugin::getParentObjectsForObject(relation, childid);
  581. LOG_PLUGIN_DEBUG("%s Relation: Group member", __FUNCTION__);
  582. findUserID(childid.id, &pws, buffer);
  583. username = pws.pw_name; // make sure we have a _copy_ of the string, not just another pointer
  584. try {
  585. findGroupID(tostring(pws.pw_gid), &grs, buffer);
  586. objectlist->push_back(objectsignature_t(objectid_t(tostring(grs.gr_gid), DISTLIST_SECURITY), grs.gr_name));
  587. } catch (std::exception &e) {
  588. // Ignore error
  589. }
  590. transform(exceptgids.begin(), exceptgids.end(), inserter(exceptgidset, exceptgidset.begin()), fromstring<const std::string,gid_t>);
  591. // This is a rather expensive operation: loop through all the
  592. // groups, and check each member for each group.
  593. ulock_normal biglock(m_plugin_lock);
  594. setgrent();
  595. while (true) {
  596. if (getgrent_r(&grs, buffer, PWBUFSIZE, &gr) != 0)
  597. break;
  598. if (gr == NULL)
  599. break;
  600. // system users don't have kopano accounts
  601. if (gr->gr_gid < mingid || gr->gr_gid >= maxgid)
  602. continue;
  603. if (exceptgidset.find(gr->gr_gid) != exceptgidset.end())
  604. continue;
  605. for (int i = 0; gr->gr_mem[i] != NULL; ++i)
  606. if (strcmp(username.c_str(), gr->gr_mem[i]) == 0) {
  607. objectlist->push_back(objectsignature_t(objectid_t(tostring(gr->gr_gid), DISTLIST_SECURITY), gr->gr_name));
  608. break;
  609. }
  610. }
  611. endgrent();
  612. biglock.unlock();
  613. // because users can be explicitly listed in their default group
  614. objectlist->sort();
  615. objectlist->unique();
  616. return objectlist;
  617. }
  618. std::unique_ptr<signatures_t>
  619. UnixUserPlugin::getSubObjectsForObject(userobject_relation_t relation,
  620. const objectid_t &parentid)
  621. {
  622. std::unique_ptr<signatures_t> objectlist(new signatures_t());
  623. char buffer[PWBUFSIZE];
  624. struct passwd pws, *pw = NULL;
  625. struct group grp;
  626. uid_t minuid = fromstring<const char *, uid_t>(m_config->GetSetting("min_user_uid"));
  627. uid_t maxuid = fromstring<const char *, uid_t>(m_config->GetSetting("max_user_uid"));
  628. const char *lpszNonActive = m_config->GetSetting("non_login_shell");
  629. gid_t mingid = fromstring<const char *, gid_t>(m_config->GetSetting("min_group_gid"));
  630. gid_t maxgid = fromstring<const char *, gid_t>(m_config->GetSetting("max_group_gid"));
  631. vector<string> exceptuids = tokenize(m_config->GetSetting("except_user_uids"), " \t");
  632. set<uid_t> exceptuidset;
  633. objectid_t objectid;
  634. if (relation != OBJECTRELATION_GROUP_MEMBER)
  635. return DBPlugin::getSubObjectsForObject(relation, parentid);
  636. LOG_PLUGIN_DEBUG("%s Relation: Group member", __FUNCTION__);
  637. findGroupID(parentid.id, &grp, buffer);
  638. for (unsigned int i = 0; grp.gr_mem[i] != NULL; ++i)
  639. try {
  640. objectlist->push_back(resolveUserName(grp.gr_mem[i]));
  641. } catch (std::exception &e) {
  642. // Ignore error
  643. }
  644. transform(exceptuids.begin(), exceptuids.end(), inserter(exceptuidset, exceptuidset.begin()), fromstring<const std::string,uid_t>);
  645. // iterate through /etc/passwd users to find default group (eg. users) and add it to the list
  646. ulock_normal biglock(m_plugin_lock);
  647. setpwent();
  648. while (true) {
  649. if (getpwent_r(&pws, buffer, PWBUFSIZE, &pw) != 0)
  650. break;
  651. if (pw == NULL)
  652. break;
  653. // system users don't have kopano accounts
  654. if (pw->pw_uid < minuid || pw->pw_uid >= maxuid)
  655. continue;
  656. if (exceptuidset.find(pw->pw_uid) != exceptuidset.end())
  657. continue;
  658. // is it a member, and fits the default group in the range?
  659. if (pw->pw_gid == grp.gr_gid && pw->pw_gid >= mingid && pw->pw_gid < maxgid) {
  660. if (strcmp(pw->pw_shell, lpszNonActive) != 0)
  661. objectid = objectid_t(tostring(pw->pw_uid), ACTIVE_USER);
  662. else
  663. objectid = objectid_t(tostring(pw->pw_uid), NONACTIVE_USER);
  664. objectlist->push_back(objectsignature_t(objectid, getDBSignature(objectid) + pw->pw_gecos + pw->pw_name));
  665. }
  666. }
  667. endpwent();
  668. biglock.unlock();
  669. // because users can be explicitly listed in their default group
  670. objectlist->sort();
  671. objectlist->unique();
  672. return objectlist;
  673. }
  674. void UnixUserPlugin::addSubObjectRelation(userobject_relation_t relation, const objectid_t &id, const objectid_t &member)
  675. {
  676. if (relation != OBJECTRELATION_QUOTA_USERRECIPIENT && relation != OBJECTRELATION_USER_SENDAS)
  677. throw notimplemented("Adding object relations is not supported when using the Unix user plugin.");
  678. DBPlugin::addSubObjectRelation(relation, id, member);
  679. }
  680. void UnixUserPlugin::deleteSubObjectRelation(userobject_relation_t relation, const objectid_t &id, const objectid_t &member)
  681. {
  682. if (relation != OBJECTRELATION_QUOTA_USERRECIPIENT && relation != OBJECTRELATION_USER_SENDAS)
  683. throw notimplemented("Deleting object relations is not supported when using the Unix user plugin.");
  684. DBPlugin::deleteSubObjectRelation(relation, id, member);
  685. }
  686. std::unique_ptr<signatures_t>
  687. UnixUserPlugin::searchObject(const std::string &match, unsigned int ulFlags)
  688. {
  689. char buffer[PWBUFSIZE];
  690. struct passwd pws, *pw = NULL;
  691. std::unique_ptr<signatures_t> objectlist(new signatures_t());
  692. std::unique_ptr<signatures_t> objects;
  693. LOG_PLUGIN_DEBUG("%s %s flags:%x", __FUNCTION__, match.c_str(), ulFlags);
  694. ulock_normal biglock(m_plugin_lock);
  695. objects = getAllUserObjects(match, ulFlags);
  696. objectlist->merge(*objects.get());
  697. objects = getAllGroupObjects(match, ulFlags);
  698. objectlist->merge(*objects.get());
  699. biglock.unlock();
  700. // See if we get matches based on database details as well
  701. try {
  702. const char *search_props[] = { OP_EMAILADDRESS, NULL };
  703. objects = DBPlugin::searchObjects(match, search_props, NULL, ulFlags);
  704. for (const auto &sig : *objects) {
  705. // the DBPlugin returned the DB signature, so we need to prepend this with the gecos signature
  706. int ret = getpwuid_r(atoi(sig.id.id.c_str()), &pws, buffer, PWBUFSIZE, &pw);
  707. if (ret != 0)
  708. errnoCheck(sig.id.id, ret);
  709. if (pw == NULL) // object not found anymore
  710. continue;
  711. objectlist->push_back(objectsignature_t(sig.id, sig.signature + pw->pw_gecos + pw->pw_name));
  712. }
  713. } catch (objectnotfound &e) {
  714. // Ignore exception, we will check lObjects.empty() later.
  715. } // All other exceptions should be thrown further up the chain.
  716. objectlist->sort();
  717. objectlist->unique();
  718. if (objectlist->empty())
  719. throw objectnotfound(string("unix_plugin: no match: ") + match);
  720. return objectlist;
  721. }
  722. std::unique_ptr<objectdetails_t> UnixUserPlugin::getPublicStoreDetails(void)
  723. {
  724. throw notsupported("public store details");
  725. }
  726. std::unique_ptr<serverdetails_t>
  727. UnixUserPlugin::getServerDetails(const std::string &server)
  728. {
  729. throw notsupported("server details");
  730. }
  731. std::unique_ptr<serverlist_t> UnixUserPlugin::getServers(void)
  732. {
  733. throw notsupported("server list");
  734. }
  735. std::unique_ptr<std::map<objectid_t, objectdetails_t> >
  736. UnixUserPlugin::getObjectDetails(const std::list<objectid_t> &objectids)
  737. {
  738. std::unique_ptr<std::map<objectid_t, objectdetails_t> > mapdetails(new map<objectid_t, objectdetails_t>());
  739. std::unique_ptr<objectdetails_t> uDetails;
  740. objectdetails_t details;
  741. if (objectids.empty())
  742. return mapdetails;
  743. for (const auto &id : objectids) {
  744. try {
  745. uDetails = this->getObjectDetails(id);
  746. }
  747. catch (objectnotfound &e) {
  748. // ignore not found error
  749. continue;
  750. }
  751. (*mapdetails)[id] = (*uDetails.get());
  752. }
  753. return mapdetails;
  754. }
  755. // -------------
  756. // private
  757. // -------------
  758. std::unique_ptr<objectdetails_t>
  759. UnixUserPlugin::objectdetailsFromPwent(struct passwd *pw)
  760. {
  761. std::unique_ptr<objectdetails_t> ud(new objectdetails_t());
  762. string gecos;
  763. size_t comma;
  764. ud->SetPropString(OB_PROP_S_LOGIN, string(pw->pw_name));
  765. if (strcmp(pw->pw_shell, m_config->GetSetting("non_login_shell")) == 0)
  766. ud->SetClass(NONACTIVE_USER);
  767. else
  768. ud->SetClass(ACTIVE_USER);
  769. gecos = m_iconv->convert(pw->pw_gecos);
  770. // gecos may contain room/phone number etc. too
  771. comma = gecos.find(",");
  772. if (comma != string::npos) {
  773. ud->SetPropString(OB_PROP_S_FULLNAME, gecos.substr(0,comma));
  774. } else {
  775. ud->SetPropString(OB_PROP_S_FULLNAME, gecos);
  776. }
  777. if (!strcmp(pw->pw_passwd, "x")) {
  778. // shadow password entry
  779. struct spwd spws, *spw = NULL;
  780. char sbuffer[PWBUFSIZE];
  781. if (getspnam_r(pw->pw_name, &spws, sbuffer, PWBUFSIZE, &spw) != 0) {
  782. ec_log_warn("getspname_r: %s", strerror(errno));
  783. /* set invalid password entry, cannot login without a password */
  784. ud->SetPropString(OB_PROP_S_PASSWORD, std::string("x"));
  785. } else if (spw == NULL) {
  786. // invalid entry, must have a shadow password set in this case
  787. // throw objectnotfound(ud->id);
  788. // too bad that the password couldn't be found, but it's not that critical
  789. ec_log_warn("Warning: unable to find password for user \"%s\": %s", pw->pw_name, strerror(errno));
  790. ud->SetPropString(OB_PROP_S_PASSWORD, string("x")); // set invalid password entry, cannot login without a password
  791. } else {
  792. ud->SetPropString(OB_PROP_S_PASSWORD, string(spw->sp_pwdp));
  793. }
  794. } else if (!strcmp(pw->pw_passwd, "*") || !strcmp(pw->pw_passwd, "!")){
  795. throw objectnotfound(string());
  796. } else {
  797. ud->SetPropString(OB_PROP_S_PASSWORD, string(pw->pw_passwd));
  798. }
  799. // This may be overridden by settings in the database
  800. ud->SetPropString(OB_PROP_S_EMAIL, string(pw->pw_name) + "@" + m_config->GetSetting("default_domain"));
  801. return ud;
  802. }
  803. std::unique_ptr<objectdetails_t>
  804. UnixUserPlugin::objectdetailsFromGrent(struct group *gr)
  805. {
  806. std::unique_ptr<objectdetails_t> gd(new objectdetails_t(DISTLIST_SECURITY));
  807. gd->SetPropString(OB_PROP_S_LOGIN, string(gr->gr_name));
  808. gd->SetPropString(OB_PROP_S_FULLNAME, string(gr->gr_name));
  809. return gd;
  810. }
  811. std::string UnixUserPlugin::getDBSignature(const objectid_t &id)
  812. {
  813. string strQuery;
  814. DB_RESULT lpResult;
  815. DB_ROW lpDBRow = NULL;
  816. ECRESULT er = erSuccess;
  817. strQuery =
  818. "SELECT op.value "
  819. "FROM " + (string)DB_OBJECTPROPERTY_TABLE + " AS op "
  820. "JOIN " + (string)DB_OBJECT_TABLE + " AS o "
  821. "ON op.objectid = o.id "
  822. "WHERE o.externid = '" + m_lpDatabase->Escape(id.id) + "' "
  823. "AND o.objectclass = " + stringify(id.objclass) + " "
  824. "AND op.propname = '" + OP_MODTIME + "'";
  825. er = m_lpDatabase->DoSelect(strQuery, &lpResult);
  826. if (er != erSuccess)
  827. return string();
  828. lpDBRow = m_lpDatabase->FetchRow(lpResult);
  829. if (lpDBRow == NULL || lpDBRow[0] == NULL)
  830. return string();
  831. return lpDBRow[0];
  832. }
  833. void UnixUserPlugin::errnoCheck(const std::string &user, int e) const
  834. {
  835. if (e != 0) {
  836. char buffer[256];
  837. char *retbuf;
  838. retbuf = strerror_r(e, buffer, 256);
  839. // from the getpwnam() man page: (notice the last or...)
  840. // ERRORS
  841. // 0 or ENOENT or ESRCH or EBADF or EPERM or ...
  842. // The given name or uid was not found.
  843. switch (e) {
  844. // 0 is handled in top if()
  845. case ENOENT:
  846. case ESRCH:
  847. case EBADF:
  848. case EPERM:
  849. // calling function must check pw == NULL to throw objectnotfound()
  850. break;
  851. default:
  852. // broken system .. do not delete user from database
  853. throw runtime_error(string("unable to query for user ")+user+string(". Error: ")+retbuf);
  854. };
  855. }
  856. }