syncserv.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. /* --- BEGIN COPYRIGHT BLOCK ---
  2. * This Program is free software; you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation; version 2 of the License.
  5. *
  6. * This Program is distributed in the hope that it will be useful, but WITHOUT
  7. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. *
  10. * You should have received a copy of the GNU General Public License along with
  11. * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. * Place, Suite 330, Boston, MA 02111-1307 USA.
  13. *
  14. * In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. * right to link the code of this Program with code not covered under the GNU
  16. * General Public License ("Non-GPL Code") and to distribute linked combinations
  17. * including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. * permitted under this exception must only link to the code of this Program
  19. * through those well defined interfaces identified in the file named EXCEPTION
  20. * found in the source code files (the "Approved Interfaces"). The files of
  21. * Non-GPL Code may instantiate templates or use macros or inline functions from
  22. * the Approved Interfaces without causing the resulting work to be covered by
  23. * the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. * additions to the list of Approved Interfaces. You must obey the GNU General
  25. * Public License in all respects for all of the Program code and other code used
  26. * in conjunction with the Program except the Non-GPL Code covered by this
  27. * exception. If you modify this file, you may extend this exception to your
  28. * version of the file, but you are not obligated to do so. If you do not wish to
  29. * provide this exception without modification, you must delete this exception
  30. * statement from your version and license this file solely under the GPL without
  31. * exception.
  32. *
  33. *
  34. * Copyright (C) 2005 Red Hat, Inc.
  35. * All rights reserved.
  36. * --- END COPYRIGHT BLOCK --- */
  37. // Created: 2-8-2005
  38. // Author(s): Scott Bridges
  39. #include "syncserv.h"
  40. #include "prerror.h"
  41. static char* certdbh;
  42. // ****************************************************************
  43. // passwdcb
  44. // ****************************************************************
  45. char* passwdcb(PK11SlotInfo* info, PRBool retry, void* arg)
  46. {
  47. char* result = NULL;
  48. unsigned long resultLen = 0;
  49. DWORD type;
  50. HKEY regKey;
  51. if (!retry)
  52. {
  53. RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\PasswordSync", &regKey);
  54. RegQueryValueEx(regKey, "Install Path", NULL, &type, NULL, &resultLen);
  55. result = (char*)malloc(resultLen);
  56. RegQueryValueEx(regKey, "Cert Token", NULL, &type, (unsigned char*)result, &resultLen);
  57. RegCloseKey(regKey);
  58. }
  59. return result;
  60. }
  61. // ****************************************************************
  62. // PassSyncService::PassSyncService
  63. // ****************************************************************
  64. PassSyncService::PassSyncService(const TCHAR *serviceName) : CNTService(serviceName)
  65. {
  66. char sysPath[SYNCSERV_BUF_SIZE];
  67. char tempRegBuff[SYNCSERV_BUF_SIZE];
  68. HKEY regKey;
  69. DWORD type;
  70. unsigned long size;
  71. passhookEventHandle = CreateEvent(NULL, FALSE, FALSE, PASSHAND_EVENT_NAME);
  72. mainLdapConnection = NULL;
  73. results = NULL;
  74. currentResult = NULL;
  75. lastLdapError = LDAP_SUCCESS;
  76. certdbh = NULL;
  77. RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\PasswordSync", &regKey);
  78. size = SYNCSERV_BUF_SIZE;
  79. if(RegQueryValueEx(regKey, "Log Level", NULL, &type, (unsigned char*)tempRegBuff, &size) == ERROR_SUCCESS)
  80. {
  81. logLevel = (unsigned long)atoi(tempRegBuff);
  82. }
  83. else
  84. {
  85. logLevel = 0;
  86. }
  87. size = SYNCSERV_BUF_SIZE;
  88. if(RegQueryValueEx(regKey, "Time To Live", NULL, &type, (unsigned char*)tempRegBuff, &size) == ERROR_SUCCESS)
  89. {
  90. maxBackoffTime = (unsigned long)atoi(tempRegBuff);
  91. }
  92. else
  93. {
  94. maxBackoffTime = (1 << 12) * SYNCSERV_BASE_BACKOFF_LEN;
  95. }
  96. size = SYNCSERV_BUF_SIZE;
  97. RegQueryValueEx(regKey, "Install Path", NULL, &type, (unsigned char*)installPath, &size);
  98. size = SYNCSERV_BUF_SIZE;
  99. RegQueryValueEx(regKey, "Host Name", NULL, &type, (unsigned char*)ldapHostName, &size);
  100. size = SYNCSERV_BUF_SIZE;
  101. RegQueryValueEx(regKey, "Port Number", NULL, &type, (unsigned char*)ldapHostPort, &size);
  102. size = SYNCSERV_BUF_SIZE;
  103. RegQueryValueEx(regKey, "User Name", NULL, &type, (unsigned char*)ldapAuthUsername, &size);
  104. size = SYNCSERV_BUF_SIZE;
  105. RegQueryValueEx(regKey, "Password", NULL, &type, (unsigned char*)ldapAuthPassword, &size);
  106. size = SYNCSERV_BUF_SIZE;
  107. RegQueryValueEx(regKey, "Search Base", NULL, &type, (unsigned char*)ldapSearchBase, &size);
  108. size = SYNCSERV_BUF_SIZE;
  109. RegQueryValueEx(regKey, "User Name Field", NULL, &type, (unsigned char*)ldapUsernameField, &size);
  110. size = SYNCSERV_BUF_SIZE;
  111. RegQueryValueEx(regKey, "Password Field", NULL, &type, (unsigned char*)ldapPasswordField, &size);
  112. RegCloseKey(regKey);
  113. ExpandEnvironmentStrings("%SystemRoot%", sysPath, SYNCSERV_BUF_SIZE);
  114. _snprintf(certPath, SYNCSERV_BUF_SIZE, "%s", installPath);
  115. _snprintf(logPath, SYNCSERV_BUF_SIZE, "%spasssync.log", installPath);
  116. _snprintf(dataFilename, SYNCSERV_BUF_SIZE, "%s\\system32\\passhook.dat", sysPath);
  117. outLog.open(logPath, ios::out | ios::app);
  118. if(outLog.is_open())
  119. {
  120. timeStamp(&outLog);
  121. outLog << "PassSync service started" << endl;
  122. }
  123. PK11_SetPasswordFunc(passwdcb);
  124. isRunning = false;
  125. }
  126. // ****************************************************************
  127. // PassSyncService::~PassSyncService
  128. // ****************************************************************
  129. PassSyncService::~PassSyncService()
  130. {
  131. if(outLog.is_open())
  132. {
  133. timeStamp(&outLog);
  134. outLog << "PassSync service stopped" << endl;
  135. }
  136. outLog.close();
  137. }
  138. // ****************************************************************
  139. //
  140. // ****************************************************************
  141. void PassSyncService::OnStop()
  142. {
  143. isRunning = false;
  144. SetEvent(passhookEventHandle);
  145. }
  146. // ****************************************************************
  147. //
  148. // ****************************************************************
  149. void PassSyncService::OnShutdown()
  150. {
  151. isRunning = false;
  152. SetEvent(passhookEventHandle);
  153. }
  154. // ****************************************************************
  155. // PassSyncService::Run
  156. // ****************************************************************
  157. void PassSyncService::Run()
  158. {
  159. isRunning = true;
  160. // Initialize NSS
  161. if(ldapssl_client_init(certPath, &certdbh) != 0)
  162. {
  163. timeStamp(&outLog);
  164. outLog << "Error initializing SSL: err=" << PR_GetError() << endl;
  165. timeStamp(&outLog);
  166. outLog << "Ensure that your SSL is setup correctly" << endl;
  167. goto exit;
  168. }
  169. SyncPasswords();
  170. while(isRunning)
  171. {
  172. if(passInfoList.empty())
  173. {
  174. if(logLevel > 0) {
  175. timeStamp(&outLog);
  176. outLog << "Password list is empty. Waiting for passhook event" << endl;
  177. }
  178. WaitForSingleObject(passhookEventHandle, INFINITE);
  179. if(logLevel > 0) {
  180. timeStamp(&outLog);
  181. outLog << "Received passhook event. Attempting sync" << endl;
  182. }
  183. }
  184. else
  185. {
  186. if(logLevel > 0) {
  187. timeStamp(&outLog);
  188. outLog << "Backing off for " << BackoffTime(GetMinBackoff()) << "ms" << endl;
  189. }
  190. WaitForSingleObject(passhookEventHandle, BackoffTime(GetMinBackoff()));
  191. if(logLevel > 0) {
  192. timeStamp(&outLog);
  193. outLog << "Backoff time expired. Attempting sync" << endl;
  194. }
  195. }
  196. SyncPasswords();
  197. UpdateBackoff();
  198. ResetEvent(passhookEventHandle);
  199. }
  200. if(passInfoList.size() > 0)
  201. {
  202. if(saveSet(&passInfoList, dataFilename) == 0)
  203. {
  204. if(logLevel > 0)
  205. {
  206. timeStamp(&outLog);
  207. outLog << passInfoList.size() << " entries saved to data file" << endl;
  208. }
  209. }
  210. else
  211. {
  212. timeStamp(&outLog);
  213. outLog << "Failed to save entries to data file" << endl;
  214. }
  215. }
  216. exit:
  217. CloseHandle(passhookEventHandle);
  218. }
  219. // ****************************************************************
  220. // PassSyncService::SyncPasswords
  221. // ****************************************************************
  222. int PassSyncService::SyncPasswords()
  223. {
  224. int result = 0;
  225. PASS_INFO_LIST emptyPassInfoList;
  226. PASS_INFO_LIST_ITERATOR currentPassInfo;
  227. PASS_INFO_LIST_ITERATOR tempPassInfo;
  228. char* dn;
  229. int tempSize = passInfoList.size();
  230. if(loadSet(&passInfoList, dataFilename) == 0)
  231. {
  232. if((passInfoList.size() - tempSize) > 0)
  233. {
  234. if(logLevel > 0)
  235. {
  236. timeStamp(&outLog);
  237. outLog << passInfoList.size() - tempSize << " new entries loaded from data file" << endl;
  238. }
  239. if(saveSet(&emptyPassInfoList, dataFilename) == 0)
  240. {
  241. if(logLevel > 0)
  242. {
  243. timeStamp(&outLog);
  244. outLog << "Cleared contents of data file" << endl;
  245. }
  246. }
  247. else
  248. {
  249. timeStamp(&outLog);
  250. outLog << "Failed to clear contents of data file" << endl;
  251. }
  252. }
  253. }
  254. else
  255. {
  256. timeStamp(&outLog);
  257. outLog << "Failed to load entries from file" << endl;
  258. }
  259. if(passInfoList.size() > 0)
  260. {
  261. if(logLevel > 0)
  262. {
  263. timeStamp(&outLog);
  264. outLog << "Password list has " << passInfoList.size() << " entries" << endl;
  265. }
  266. }
  267. if(Connect(&mainLdapConnection, ldapAuthUsername, ldapAuthPassword) < 0)
  268. {
  269. // log connection failure.
  270. timeStamp(&outLog);
  271. outLog << "Can not connect to ldap server in SyncPasswords" << endl;
  272. goto exit;
  273. }
  274. currentPassInfo = passInfoList.begin();
  275. while(currentPassInfo != passInfoList.end())
  276. {
  277. if(logLevel > 0)
  278. {
  279. timeStamp(&outLog);
  280. outLog << "Attempting to sync password for " << currentPassInfo->username << endl;
  281. }
  282. if(QueryUsername(currentPassInfo->username) == 0)
  283. {
  284. while((dn = GetDN()) != NULL)
  285. {
  286. if(FutureOccurrence(currentPassInfo))
  287. {
  288. if(logLevel > 0)
  289. {
  290. timeStamp(&outLog);
  291. outLog << "Newer password changes for " << currentPassInfo->username << " exist" << endl;
  292. }
  293. }
  294. else if(MultipleResults() && !SYNCSERV_ALLOW_MULTI_MOD)
  295. {
  296. timeStamp(&outLog);
  297. outLog << "Multiple results not allowed: " << currentPassInfo->username << endl;
  298. }
  299. else if(CanBind(dn, currentPassInfo->password))
  300. {
  301. if(logLevel > 0)
  302. {
  303. timeStamp(&outLog);
  304. outLog << "Password match, no modify performed: " << currentPassInfo->username << endl;
  305. }
  306. }
  307. else if(ModifyPassword(dn, currentPassInfo->password) != 0)
  308. {
  309. // log modify failure.
  310. timeStamp(&outLog);
  311. outLog << "Modify password failed for remote entry: " << dn << endl;
  312. // defer this change for later
  313. timeStamp(&outLog);
  314. outLog << "Deferring password change for " << currentPassInfo->username << endl;
  315. currentPassInfo++;
  316. continue;
  317. }
  318. else
  319. {
  320. if(logLevel > 0)
  321. {
  322. timeStamp(&outLog);
  323. outLog << "Password modified for remote entry: " << dn << endl;
  324. }
  325. }
  326. tempPassInfo = currentPassInfo;
  327. currentPassInfo++;
  328. if(logLevel > 0)
  329. {
  330. timeStamp(&outLog);
  331. outLog << "Removing password change from list" << endl;
  332. }
  333. passInfoList.erase(tempPassInfo);
  334. }
  335. }
  336. else
  337. {
  338. if(logLevel > 0)
  339. {
  340. timeStamp(&outLog);
  341. outLog << "Deferring password change for " << currentPassInfo->username << endl;
  342. }
  343. currentPassInfo++;
  344. }
  345. }
  346. exit:
  347. if(mainLdapConnection != NULL)
  348. {
  349. Disconnect(&mainLdapConnection);
  350. }
  351. return result;
  352. }
  353. // ****************************************************************
  354. // PassSyncService::Connect
  355. // ****************************************************************
  356. int PassSyncService::Connect(LDAP** connection, char* dn, char* auth)
  357. {
  358. int result = 0;
  359. *connection = ldapssl_init(ldapHostName, atoi(ldapHostPort), 1);
  360. if(*connection == NULL)
  361. {
  362. timeStamp(&outLog);
  363. outLog << "ldapssl_init failed in Connect" << endl;
  364. result = -1;
  365. goto exit;
  366. }
  367. lastLdapError = ldap_simple_bind_s(*connection, dn, auth);
  368. if(lastLdapError != LDAP_SUCCESS)
  369. {
  370. // Log error if we're binding as ldapAuthUsername
  371. if(strcmp(dn, ldapAuthUsername) == 0)
  372. {
  373. timeStamp(&outLog);
  374. outLog << "Ldap bind error in Connect" << endl;
  375. outLog << "\t" << lastLdapError << ": " << ldap_err2string(lastLdapError) << endl;
  376. }
  377. result = -1;
  378. goto exit;
  379. }
  380. exit:
  381. return result;
  382. }
  383. // ****************************************************************
  384. // PassSyncService::Disconnect
  385. // ****************************************************************
  386. int PassSyncService::Disconnect(LDAP** connection)
  387. {
  388. ldap_unbind(*connection);
  389. *connection = NULL;
  390. return 0;
  391. }
  392. // ****************************************************************
  393. // PassSyncService::QueryUsername
  394. // ****************************************************************
  395. int PassSyncService::QueryUsername(char* username)
  396. {
  397. int result = 0;
  398. char searchFilter[SYNCSERV_BUF_SIZE];
  399. results = NULL;
  400. _snprintf(searchFilter, SYNCSERV_BUF_SIZE, "(%s=%s)", ldapUsernameField, username);
  401. if(logLevel > 0)
  402. {
  403. timeStamp(&outLog);
  404. outLog << "Searching for (" << ldapUsernameField << "=" << username << ")" << endl;
  405. }
  406. lastLdapError = ldap_search_ext_s(mainLdapConnection, ldapSearchBase, LDAP_SCOPE_SUBTREE, searchFilter, NULL, 0, NULL, NULL, NULL, -1, &results);
  407. if(lastLdapError != LDAP_SUCCESS)
  408. {
  409. // log reason for search failure.
  410. timeStamp(&outLog);
  411. outLog << "Ldap error in QueryUsername" << endl;
  412. outLog << "\t" << lastLdapError << ": " << ldap_err2string(lastLdapError) << endl;
  413. result = -1;
  414. goto exit;
  415. }
  416. if(ldap_first_entry(mainLdapConnection, results) == NULL)
  417. {
  418. if(logLevel > 0)
  419. {
  420. timeStamp(&outLog);
  421. outLog << "There are no entries that match: " << username << endl;
  422. }
  423. result = -1;
  424. goto exit;
  425. }
  426. exit:
  427. return result;
  428. }
  429. // ****************************************************************
  430. // PassSyncService::GetDN
  431. // ****************************************************************
  432. char* PassSyncService::GetDN()
  433. {
  434. char* result = NULL;
  435. if(currentResult == NULL)
  436. {
  437. currentResult = ldap_first_entry(mainLdapConnection, results);
  438. }
  439. else
  440. {
  441. currentResult = ldap_next_entry(mainLdapConnection, currentResult);
  442. }
  443. result = ldap_get_dn(mainLdapConnection, currentResult);
  444. return result;
  445. }
  446. // ****************************************************************
  447. // PassSyncService::ModifyPassword
  448. // ****************************************************************
  449. int PassSyncService::ModifyPassword(char* dn, char* password)
  450. {
  451. int result = 0;
  452. LDAPMod passMod;
  453. LDAPMod* mods[2] = {&passMod, NULL};
  454. char* modValues[2] = {password, NULL};
  455. passMod.mod_type = ldapPasswordField;
  456. passMod.mod_op = LDAP_MOD_REPLACE;
  457. passMod.mod_values = modValues;
  458. lastLdapError = ldap_modify_ext_s(mainLdapConnection, dn, mods, NULL, NULL);
  459. if(lastLdapError != LDAP_SUCCESS)
  460. {
  461. // log reason for modify failure.
  462. timeStamp(&outLog);
  463. outLog << "Ldap error in ModifyPassword" << endl;
  464. outLog << "\t" << lastLdapError << ": " << ldap_err2string(lastLdapError) << endl;
  465. result = -1;
  466. }
  467. return result;
  468. }
  469. // ****************************************************************
  470. // PassSyncService::FutureOccurrence
  471. // ****************************************************************
  472. bool PassSyncService::FutureOccurrence(PASS_INFO_LIST_ITERATOR startingPassInfo)
  473. {
  474. bool result = false;
  475. PASS_INFO_LIST_ITERATOR currentPassInfo;
  476. if((startingPassInfo != NULL) && (startingPassInfo != passInfoList.end()))
  477. {
  478. currentPassInfo = startingPassInfo;
  479. currentPassInfo++;
  480. while((currentPassInfo != passInfoList.end()) && (!result))
  481. {
  482. if(strcmp(currentPassInfo->username, startingPassInfo->username) == 0)
  483. {
  484. result = true;
  485. }
  486. currentPassInfo++;
  487. }
  488. }
  489. return result;
  490. }
  491. // ****************************************************************
  492. // PassSyncService::MultipleResults
  493. // ****************************************************************
  494. bool PassSyncService::MultipleResults()
  495. {
  496. bool result = false;
  497. if(ldap_next_entry(mainLdapConnection, ldap_first_entry(mainLdapConnection, results)) != NULL)
  498. {
  499. result = true;
  500. }
  501. return result;
  502. }
  503. // ****************************************************************
  504. // PassSyncService::CanBind
  505. // ****************************************************************
  506. bool PassSyncService::CanBind(char* dn, char* password)
  507. {
  508. bool result;
  509. LDAP* tempConnection = NULL;
  510. if(Connect(&tempConnection, dn, password) == 0)
  511. {
  512. result = true;
  513. }
  514. else
  515. {
  516. result = false;
  517. }
  518. if(tempConnection != NULL)
  519. {
  520. Disconnect(&tempConnection);
  521. }
  522. return result;
  523. }
  524. // ****************************************************************
  525. // PassSyncService::BackoffTime
  526. // ****************************************************************
  527. unsigned long PassSyncService::BackoffTime(int backoff)
  528. {
  529. unsigned long backoffTime = 0;
  530. if(backoff > 0)
  531. {
  532. backoffTime = (1 << backoff) * SYNCSERV_BASE_BACKOFF_LEN;
  533. }
  534. return backoffTime;
  535. }
  536. // ****************************************************************
  537. // PassSyncService::UpdateBackoff
  538. // ****************************************************************
  539. void PassSyncService::UpdateBackoff()
  540. {
  541. PASS_INFO_LIST_ITERATOR currentPassInfo;
  542. PASS_INFO_LIST_ITERATOR tempPassInfo;
  543. time_t currentTime;
  544. time(&currentTime);
  545. currentPassInfo = passInfoList.begin();
  546. while(currentPassInfo != passInfoList.end())
  547. {
  548. if(((unsigned long)currentPassInfo->atTime + (BackoffTime(currentPassInfo->backoffCount) / 1000)) <= (unsigned long)currentTime)
  549. {
  550. currentPassInfo->backoffCount++;
  551. }
  552. if(((unsigned long)currentTime - (unsigned long)currentPassInfo->atTime) > (maxBackoffTime / 1000))
  553. {
  554. timeStamp(&outLog);
  555. outLog << "Abandoning password change for " << currentPassInfo->username << ", backoff expired" << endl;
  556. tempPassInfo = currentPassInfo;
  557. currentPassInfo++;
  558. passInfoList.erase(tempPassInfo);
  559. }
  560. else
  561. {
  562. currentPassInfo++;
  563. }
  564. }
  565. }
  566. // ****************************************************************
  567. // PassSyncService::GetMinBackoff
  568. // ****************************************************************
  569. int PassSyncService::GetMinBackoff()
  570. {
  571. PASS_INFO_LIST_ITERATOR currentPassInfo;
  572. unsigned long minBackoff = INFINITE;
  573. for(currentPassInfo = passInfoList.begin(); currentPassInfo != passInfoList.end(); currentPassInfo++)
  574. {
  575. if((unsigned long)currentPassInfo->backoffCount < minBackoff)
  576. {
  577. minBackoff = currentPassInfo->backoffCount;
  578. }
  579. }
  580. return minBackoff;
  581. }