ldaputils.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. // LDAP prototypes use char* where they should be using const char *, resulting in lots of spurious warnings
  14. #pragma warning( disable : 4786 )
  15. #ifdef __GNUC__
  16. #pragma GCC diagnostic ignored "-Wwrite-strings"
  17. #endif
  18. #include "ldaputils.hpp"
  19. #ifndef _WIN32
  20. # include <signal.h>
  21. #endif
  22. //------------------------------------
  23. // LdapUtils implementation
  24. //------------------------------------
  25. LDAP* LdapUtils::LdapInit(const char* protocol, const char* host, int port, int secure_port, bool throwOnError)
  26. {
  27. LDAP* ld = NULL;
  28. if(stricmp(protocol, "ldaps") == 0)
  29. {
  30. #ifdef _WIN32
  31. ld = ldap_sslinit((char*)host, secure_port, 1);
  32. if (ld == NULL )
  33. throw MakeStringException(-1, "ldap_sslinit error" );
  34. int rc = 0;
  35. unsigned long version = LDAP_VERSION3;
  36. long lv = 0;
  37. rc = ldap_set_option(ld,
  38. LDAP_OPT_PROTOCOL_VERSION,
  39. (void*)&version);
  40. if (rc != LDAP_SUCCESS)
  41. throw MakeStringException(-1, "ldap_set_option error - %s", ldap_err2string(rc));
  42. rc = ldap_get_option(ld,LDAP_OPT_SSL,(void*)&lv);
  43. if (rc != LDAP_SUCCESS)
  44. throw MakeStringException(-1, "ldap_get_option error - %s", ldap_err2string(rc));
  45. // If SSL is not enabled, enable it.
  46. if ((void*)lv != LDAP_OPT_ON)
  47. {
  48. rc = ldap_set_option(ld, LDAP_OPT_SSL, LDAP_OPT_ON);
  49. if (rc != LDAP_SUCCESS)
  50. throw MakeStringException(-1, "ldap_set_option error - %s", ldap_err2string(rc));
  51. }
  52. ldap_set_option(ld, LDAP_OPT_SERVER_CERTIFICATE, verifyServerCert);
  53. #else
  54. // Initialize an LDAP session for TLS/SSL
  55. #ifndef HAVE_TLS
  56. //throw MakeStringException(-1, "openldap client library libldap not compiled with TLS support");
  57. #endif
  58. StringBuffer uri("ldaps://");
  59. uri.appendf("%s:%d", host, secure_port);
  60. DBGLOG("connecting to %s", uri.str());
  61. int rc = LDAP_INIT(&ld, uri.str());
  62. if(rc != LDAP_SUCCESS)
  63. {
  64. if (throwOnError)
  65. throw MakeStringException(-1, "ldap_initialize error %s", ldap_err2string(rc));
  66. DBGLOG("ldap_initialize error %s", ldap_err2string(rc));
  67. return nullptr;
  68. }
  69. int reqcert = LDAP_OPT_X_TLS_NEVER;
  70. ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &reqcert);
  71. #endif
  72. }
  73. else
  74. {
  75. // Initialize an LDAP session
  76. #ifdef _WIN32
  77. ld = LDAP_INIT(host, port);
  78. if(NULL == ld)
  79. {
  80. throw MakeStringException(-1, "ldap_init(%s,%d) error %s", host, port, ldap_err2string(LdapGetLastError()));
  81. }
  82. #else
  83. StringBuffer uri("ldap://");
  84. uri.appendf("%s:%d", host, port);
  85. DBGLOG("connecting to %s", uri.str());
  86. int rc = LDAP_INIT(&ld, uri.str());
  87. if(rc != LDAP_SUCCESS)
  88. {
  89. if (throwOnError)
  90. throw MakeStringException(-1, "ldap_initialize(%s,%d) error %s", host, port, ldap_err2string(rc));
  91. DBGLOG("ldap_initialize error %s", ldap_err2string(rc));
  92. return nullptr;
  93. }
  94. #endif
  95. }
  96. return ld;
  97. }
  98. int LdapUtils::LdapSimpleBind(LDAP* ld, int ldapTimeout, char* userdn, char* password)
  99. {
  100. #ifndef _WIN32
  101. TIMEVAL timeout = {ldapTimeout, 0};
  102. ldap_set_option(ld, LDAP_OPT_TIMEOUT, &timeout);
  103. ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &timeout);
  104. #endif
  105. int srtn = ldap_bind_s(ld, userdn, password, LDAP_AUTH_SIMPLE);
  106. #ifndef _WIN32
  107. // secure ldap tls might overwrite SIGPIPE handler
  108. signal(SIGPIPE, SIG_IGN);
  109. #endif
  110. return srtn;
  111. }
  112. // userdn is required for ldap_simple_bind_s, not really necessary for ldap_bind_s.
  113. int LdapUtils::LdapBind(LDAP* ld, int ldapTimeout, const char* domain, const char* username, const char* password, const char* userdn, LdapServerType server_type, const char* method)
  114. {
  115. bool binddone = false;
  116. int rc = LDAP_SUCCESS;
  117. // By default, use kerberos authentication
  118. if((method == NULL) || (strlen(method) == 0) || (stricmp(method, "kerberos") == 0))
  119. {
  120. #ifdef _WIN32
  121. if(server_type == ACTIVE_DIRECTORY)
  122. {
  123. if(username != NULL)
  124. {
  125. SEC_WINNT_AUTH_IDENTITY secIdent;
  126. secIdent.User = (unsigned char*)username;
  127. secIdent.UserLength = strlen(username);
  128. secIdent.Password = (unsigned char*)password;
  129. secIdent.PasswordLength = strlen(password);
  130. // Somehow, setting the domain makes it slower
  131. secIdent.Domain = (unsigned char*)domain;
  132. secIdent.DomainLength = strlen(domain);
  133. secIdent.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
  134. int rc = ldap_bind_s(ld, (char*)userdn, (char*)&secIdent, LDAP_AUTH_NEGOTIATE);
  135. if(rc != LDAP_SUCCESS)
  136. {
  137. DBGLOG("ldap_bind_s for user %s failed with %d - %s.", username, rc, ldap_err2string(rc));
  138. return rc;
  139. }
  140. }
  141. else
  142. {
  143. int rc = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE);
  144. if(rc != LDAP_SUCCESS)
  145. {
  146. DBGLOG("User Authentication Failed - ldap_bind_s for current user failed with %d - %s.", rc, ldap_err2string(rc));
  147. return rc;
  148. }
  149. }
  150. binddone = true;
  151. }
  152. #endif
  153. }
  154. if(!binddone)
  155. {
  156. if(userdn == NULL)
  157. {
  158. DBGLOG("userdn can't be NULL in order to bind to ldap server.");
  159. return LDAP_INVALID_CREDENTIALS;
  160. }
  161. int rc = LdapSimpleBind(ld, ldapTimeout, (char*)userdn, (char*)password);
  162. if (rc != LDAP_SUCCESS && server_type == OPEN_LDAP && strchr(userdn,','))
  163. { //Fedora389 is happier without the domain component specified
  164. StringBuffer cn(userdn);
  165. cn.replace(',',(char)NULL);
  166. if (cn.length())//disallow call if no cn
  167. rc = LdapSimpleBind(ld, ldapTimeout, (char*)cn.str(), (char*)password);
  168. }
  169. if (rc != LDAP_SUCCESS )
  170. {
  171. // For Active Directory, try binding with NT format username
  172. if(server_type == ACTIVE_DIRECTORY)
  173. {
  174. StringBuffer logonname;
  175. logonname.append(domain).append("\\").append(username);
  176. rc = LdapSimpleBind(ld, ldapTimeout, (char*)logonname.str(), (char*)password);
  177. if(rc != LDAP_SUCCESS)
  178. {
  179. #ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
  180. char *msg=NULL;
  181. ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&msg);
  182. DBGLOG("LDAP bind error for user %s with %d - %s. %s", logonname.str(), rc, ldap_err2string(rc), msg&&*msg?msg:"");
  183. ldap_memfree(msg);
  184. #else
  185. DBGLOG("LDAP bind error for user %s with 0x%" I64F "x - %s", username, (unsigned __int64) rc, ldap_err2string(rc));
  186. #endif
  187. return rc;
  188. }
  189. }
  190. else
  191. {
  192. DBGLOG("LDAP bind error for user %s with 0x%" I64F "x - %s", username, (unsigned __int64) rc, ldap_err2string(rc));
  193. return rc;
  194. }
  195. }
  196. }
  197. return rc;
  198. }
  199. LDAP* LdapUtils::ldapInitAndSimpleBind(const char* ldapserver, const char* userDN, const char* pwd, const char* ldapprotocol, int ldapport, int timeout, int * err)
  200. {
  201. LDAP* ld = LdapInit(ldapprotocol, ldapserver, ldapport, ldapport, false);
  202. if (ld == nullptr)
  203. {
  204. VStringBuffer uri("%s://%s:%d", ldapprotocol, ldapserver, ldapport);
  205. ERRLOG("ldap init error(%s)",uri.str());
  206. *err = -1;
  207. return nullptr;
  208. }
  209. *err = LdapSimpleBind(ld, timeout, (char*)userDN, (char*)pwd);
  210. if (*err != LDAP_SUCCESS)
  211. {
  212. DBGLOG("LdapSimpleBind error (%d) - %s for admin user %s", *err, ldap_err2string(*err), isEmptyString(userDN) ? "NULL" : userDN);
  213. if (!isEmptyString(userDN))
  214. DBGLOG("Please make sure your LDAP configuration 'systemBasedn' contains the complete path, including the complete 'dc=domainComponent'");
  215. return nullptr;
  216. }
  217. return ld;
  218. }
  219. int LdapUtils::getServerInfo(const char* ldapserver, const char* userDN, const char* pwd, const char* ldapprotocol, int ldapport, StringBuffer& domainDN, LdapServerType& stype, const char* domainname, int timeout)
  220. {
  221. LdapServerType deducedSType = LDAPSERVER_UNKNOWN;
  222. //First try anonymous bind using selected protocol/port
  223. int err = -1;
  224. LDAP* ld = ldapInitAndSimpleBind(ldapserver, nullptr, nullptr, ldapprotocol, ldapport, timeout, &err);
  225. //if that failed, try bind with credentials
  226. if (nullptr == ld)
  227. {
  228. ld = ldapInitAndSimpleBind(ldapserver, userDN, pwd, ldapprotocol, ldapport, timeout, &err);
  229. //if that failed, and was for ldaps, see if we can do anonymous bind using ldap/389
  230. if (nullptr == ld && strieq(ldapprotocol,"ldaps"))
  231. ld = ldapInitAndSimpleBind(ldapserver, nullptr, nullptr, "ldap", 389, timeout, &err);
  232. }
  233. if(nullptr == ld)
  234. {
  235. DBGLOG("ldap bind error (%d) - %s", err, ldap_err2string(err));
  236. // for new versions of openldap, version 2.2.*
  237. if(err == LDAP_PROTOCOL_ERROR)
  238. DBGLOG("If you're trying to connect to an OpenLdap server, make sure you have \"allow bind_v2\" enabled in slapd.conf");
  239. return err;
  240. }
  241. LDAPMessage* msg = NULL;
  242. char* attrs[] = {"namingContexts", NULL};
  243. TIMEVAL timeOut = {LDAPTIMEOUT,0};
  244. err = ldap_search_ext_s(ld, NULL, LDAP_SCOPE_BASE, "objectClass=*", attrs, false, NULL, NULL, &timeOut, LDAP_NO_LIMIT, &msg);
  245. if(err != LDAP_SUCCESS)
  246. {
  247. DBGLOG("ldap_search_ext_s error: %s", ldap_err2string( err ));
  248. if (msg)
  249. ldap_msgfree(msg);
  250. return err;
  251. }
  252. LDAPMessage* entry = ldap_first_entry(ld, msg);
  253. if(entry != NULL)
  254. {
  255. CLDAPGetValuesLenWrapper vals(ld, entry, "namingContexts");
  256. if(vals.hasValues())
  257. {
  258. int i = 0;
  259. const char* curdn;
  260. StringBuffer onedn;
  261. while((curdn = vals.queryCharValue(i)) != NULL)
  262. {
  263. if(*curdn != '\0' && (strncmp(curdn, "dc=", 3) == 0 || strncmp(curdn, "DC=", 3) == 0) && strstr(curdn,"DC=ForestDnsZones")==0 && strstr(curdn,"DC=DomainDnsZones")==0 )
  264. {
  265. if(domainDN.length() == 0)
  266. {
  267. StringBuffer curdomain;
  268. getName(curdn, curdomain);
  269. if(onedn.length() == 0)
  270. {
  271. DBGLOG("Queried '%s', selected basedn '%s'",curdn, curdomain.str());
  272. onedn.append(curdomain.str());
  273. }
  274. else
  275. DBGLOG("Ignoring %s", curdn);
  276. if(!domainname || !*domainname || stricmp(curdomain.str(), domainname) == 0)
  277. domainDN.append(curdn);
  278. }
  279. }
  280. else if(*curdn != '\0' && strcmp(curdn, "o=NetscapeRoot") == 0)
  281. {
  282. PROGLOG("Deduced LDAP Server Type 'iPlanet'");
  283. deducedSType = IPLANET;
  284. }
  285. i++;
  286. }
  287. if(domainDN.length() == 0)
  288. domainDN.append(onedn.str());
  289. if (deducedSType == LDAPSERVER_UNKNOWN)
  290. {
  291. if(i <= 1)
  292. {
  293. PROGLOG("Deduced LDAP Server Type 'OpenLDAP'");
  294. deducedSType = OPEN_LDAP;
  295. }
  296. else
  297. {
  298. PROGLOG("Deduced LDAP Server Type 'Active Directory'");
  299. deducedSType = ACTIVE_DIRECTORY;
  300. }
  301. }
  302. }
  303. }
  304. ldap_msgfree(msg);
  305. LDAP_UNBIND(ld);
  306. if (stype == LDAPSERVER_UNKNOWN)
  307. stype = deducedSType;
  308. else if (deducedSType != stype)
  309. WARNLOG("Ignoring deduced LDAP Server Type, does not match config LDAPServerType");
  310. return err;
  311. }
  312. void LdapUtils::bin2str(MemoryBuffer& from, StringBuffer& to)
  313. {
  314. const char* frombuf = from.toByteArray();
  315. char tmp[3];
  316. for(unsigned i = 0; i < from.length(); i++)
  317. {
  318. unsigned char c = frombuf[i];
  319. sprintf(tmp, "%02X", c);
  320. tmp[2] = 0;
  321. to.append("\\").append(tmp);
  322. }
  323. }
  324. void LdapUtils::normalizeDn(const char* dn, const char* basedn, StringBuffer& dnbuf)
  325. {
  326. dnbuf.clear();
  327. cleanupDn(dn, dnbuf);
  328. if(!containsBasedn(dnbuf.str()))
  329. dnbuf.append(",").append(basedn);
  330. }
  331. bool LdapUtils::containsBasedn(const char* str)
  332. {
  333. if(str == NULL || str[0] == '\0')
  334. return false;
  335. else
  336. return (strstr(str, "dc=") != NULL);
  337. }
  338. void LdapUtils::cleanupDn(const char* dn, StringBuffer& dnbuf)
  339. {
  340. if(dn == NULL || dn[0] == '\0')
  341. return;
  342. dnbuf.append(dn);
  343. dnbuf.toLowerCase();
  344. }
  345. bool LdapUtils::getDcName(const char* domain, StringBuffer& dc)
  346. {
  347. bool ret = false;
  348. #ifdef _WIN32
  349. PDOMAIN_CONTROLLER_INFO psInfo = NULL;
  350. DWORD dwErr = DsGetDcName(NULL, domain, NULL, NULL, DS_FORCE_REDISCOVERY | DS_DIRECTORY_SERVICE_REQUIRED, &psInfo);
  351. if( dwErr == NO_ERROR)
  352. {
  353. const char* dcname = psInfo->DomainControllerName;
  354. if(dcname != NULL)
  355. {
  356. while(*dcname == '\\')
  357. dcname++;
  358. dc.append(dcname);
  359. ret = true;
  360. }
  361. NetApiBufferFree(psInfo);
  362. }
  363. else
  364. {
  365. DBGLOG("Error getting domain controller, error = %d", dwErr);
  366. ret = false;
  367. }
  368. #endif
  369. return ret;
  370. }
  371. void LdapUtils::getName(const char* dn, StringBuffer& name)
  372. {
  373. const char* bptr = dn;
  374. while(*bptr != '\0' && *bptr != '=')
  375. bptr++;
  376. if(*bptr == '\0')
  377. {
  378. name.append(dn);
  379. return;
  380. }
  381. else
  382. bptr++;
  383. const char* colon = strstr(bptr, ",");
  384. if(colon == NULL)
  385. name.append(bptr);
  386. else
  387. name.append(colon - bptr, bptr);
  388. }