ldaputils.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. //------------------------------------
  20. // LdapUtils implementation
  21. //------------------------------------
  22. LDAP* LdapUtils::LdapInit(const char* protocol, const char* host, int port, int secure_port)
  23. {
  24. LDAP* ld = NULL;
  25. if(stricmp(protocol, "ldaps") == 0)
  26. {
  27. #ifdef _WIN32
  28. ld = ldap_sslinit((char*)host, secure_port, 1);
  29. if (ld == NULL )
  30. throw MakeStringException(-1, "ldap_sslinit error" );
  31. int rc = 0;
  32. unsigned long version = LDAP_VERSION3;
  33. long lv = 0;
  34. rc = ldap_set_option(ld,
  35. LDAP_OPT_PROTOCOL_VERSION,
  36. (void*)&version);
  37. if (rc != LDAP_SUCCESS)
  38. throw MakeStringException(-1, "ldap_set_option error - %s", ldap_err2string(rc));
  39. rc = ldap_get_option(ld,LDAP_OPT_SSL,(void*)&lv);
  40. if (rc != LDAP_SUCCESS)
  41. throw MakeStringException(-1, "ldap_get_option error - %s", ldap_err2string(rc));
  42. // If SSL is not enabled, enable it.
  43. if ((void*)lv != LDAP_OPT_ON)
  44. {
  45. rc = ldap_set_option(ld, LDAP_OPT_SSL, LDAP_OPT_ON);
  46. if (rc != LDAP_SUCCESS)
  47. throw MakeStringException(-1, "ldap_set_option error - %s", ldap_err2string(rc));
  48. }
  49. ldap_set_option(ld, LDAP_OPT_SERVER_CERTIFICATE, verifyServerCert);
  50. #else
  51. // Initialize an LDAP session for TLS/SSL
  52. #ifndef HAVE_TLS
  53. //throw MakeStringException(-1, "openldap client library libldap not compiled with TLS support");
  54. #endif
  55. StringBuffer uri("ldaps://");
  56. uri.appendf("%s:%d", host, secure_port);
  57. DBGLOG("connecting to %s", uri.str());
  58. int rc = LDAP_INIT(&ld, uri.str());
  59. if(rc != LDAP_SUCCESS)
  60. {
  61. throw MakeStringException(-1, "ldap_initialize error %s", ldap_err2string(rc));
  62. }
  63. int reqcert = LDAP_OPT_X_TLS_NEVER;
  64. ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &reqcert);
  65. #endif
  66. }
  67. else
  68. {
  69. // Initialize an LDAP session
  70. DBGLOG("connecting to ldap://%s:%d", host, port);
  71. #ifdef _WIN32
  72. ld = LDAP_INIT(host, port);
  73. if(NULL == ld)
  74. {
  75. throw MakeStringException(-1, "ldap_init(%s,%d) error %s", host, port, ldap_err2string(LdapGetLastError()));
  76. }
  77. #else
  78. StringBuffer uri("ldap://");
  79. uri.appendf("%s:%d", host, port);
  80. int rc = LDAP_INIT(&ld, uri.str());
  81. if(rc != LDAP_SUCCESS)
  82. {
  83. throw MakeStringException(-1, "ldap_initialize(%s,%d) error %s", host, port, ldap_err2string(rc));
  84. }
  85. #endif
  86. }
  87. return ld;
  88. }
  89. int LdapUtils::LdapSimpleBind(LDAP* ld, char* userdn, char* password)
  90. {
  91. #ifndef _WIN32
  92. TIMEVAL timeout = {LDAPTIMEOUT, 0};
  93. ldap_set_option(ld, LDAP_OPT_TIMEOUT, &timeout);
  94. ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &timeout);
  95. #endif
  96. return ldap_bind_s(ld, userdn, password, LDAP_AUTH_SIMPLE);
  97. }
  98. // userdn is required for ldap_simple_bind_s, not really necessary for ldap_bind_s.
  99. int LdapUtils::LdapBind(LDAP* ld, const char* domain, const char* username, const char* password, const char* userdn, LdapServerType server_type, const char* method)
  100. {
  101. bool binddone = false;
  102. int rc = LDAP_SUCCESS;
  103. // By default, use kerberos authentication
  104. if((method == NULL) || (strlen(method) == 0) || (stricmp(method, "kerberos") == 0))
  105. {
  106. #ifdef _WIN32
  107. if(server_type == ACTIVE_DIRECTORY)
  108. {
  109. if(username != NULL)
  110. {
  111. SEC_WINNT_AUTH_IDENTITY secIdent;
  112. secIdent.User = (unsigned char*)username;
  113. secIdent.UserLength = strlen(username);
  114. secIdent.Password = (unsigned char*)password;
  115. secIdent.PasswordLength = strlen(password);
  116. // Somehow, setting the domain makes it slower
  117. secIdent.Domain = (unsigned char*)domain;
  118. secIdent.DomainLength = strlen(domain);
  119. secIdent.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
  120. int rc = ldap_bind_s(ld, (char*)userdn, (char*)&secIdent, LDAP_AUTH_NEGOTIATE);
  121. if(rc != LDAP_SUCCESS)
  122. {
  123. DBGLOG("ldap_bind_s for user %s failed with %d - %s.", username, rc, ldap_err2string(rc));
  124. return rc;
  125. }
  126. }
  127. else
  128. {
  129. int rc = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE);
  130. if(rc != LDAP_SUCCESS)
  131. {
  132. DBGLOG("User Authentication Failed - ldap_bind_s for current user failed with %d - %s.", rc, ldap_err2string(rc));
  133. return rc;
  134. }
  135. }
  136. binddone = true;
  137. }
  138. #endif
  139. }
  140. if(!binddone)
  141. {
  142. if(userdn == NULL)
  143. {
  144. DBGLOG("userdn can't be NULL in order to bind to ldap server.");
  145. return LDAP_INVALID_CREDENTIALS;
  146. }
  147. int rc = LdapSimpleBind(ld, (char*)userdn, (char*)password);
  148. if (rc != LDAP_SUCCESS && server_type == OPEN_LDAP && strchr(userdn,','))
  149. { //Fedora389 is happier without the domain component specified
  150. StringBuffer cn(userdn);
  151. cn.replace(',',(char)NULL);
  152. if (cn.length())//disallow call if no cn
  153. rc = LdapSimpleBind(ld, (char*)cn.str(), (char*)password);
  154. }
  155. if (rc != LDAP_SUCCESS )
  156. {
  157. // For Active Directory, try binding with NT format username
  158. if(server_type == ACTIVE_DIRECTORY)
  159. {
  160. StringBuffer logonname;
  161. logonname.append(domain).append("\\").append(username);
  162. rc = LdapSimpleBind(ld, (char*)logonname.str(), (char*)password);
  163. if(rc != LDAP_SUCCESS)
  164. {
  165. #ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
  166. char *msg=NULL;
  167. ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, (void*)&msg);
  168. DBGLOG("LDAP bind error for user %s with %d - %s. %s", logonname.str(), rc, ldap_err2string(rc), msg&&*msg?msg:"");
  169. ldap_memfree(msg);
  170. #else
  171. DBGLOG("LDAP bind error for user %s with 0x%" I64F "x - %s", username, (unsigned __int64) rc, ldap_err2string(rc));
  172. #endif
  173. return rc;
  174. }
  175. }
  176. else
  177. {
  178. DBGLOG("LDAP bind error for user %s with 0x%" I64F "x - %s", username, (unsigned __int64) rc, ldap_err2string(rc));
  179. return rc;
  180. }
  181. }
  182. }
  183. return rc;
  184. }
  185. int LdapUtils::getServerInfo(const char* ldapserver, int ldapport, StringBuffer& domainDN, LdapServerType& stype, const char* domainname)
  186. {
  187. LdapServerType deducedSType = LDAPSERVER_UNKNOWN;
  188. LDAP* ld = LdapInit("ldap", ldapserver, ldapport, 636);
  189. if(ld == NULL)
  190. {
  191. ERRLOG("ldap init error");
  192. return false;
  193. }
  194. int err = LdapSimpleBind(ld, NULL, NULL);
  195. if(err != LDAP_SUCCESS)
  196. {
  197. DBGLOG("ldap anonymous bind error (%d) - %s", err, ldap_err2string(err));
  198. // for new versions of openldap, version 2.2.*
  199. if(err == LDAP_PROTOCOL_ERROR)
  200. DBGLOG("If you're trying to connect to an OpenLdap server, make sure you have \"allow bind_v2\" enabled in slapd.conf");
  201. return err;
  202. }
  203. LDAPMessage* msg = NULL;
  204. char* attrs[] = {"namingContexts", NULL};
  205. TIMEVAL timeOut = {LDAPTIMEOUT,0};
  206. err = ldap_search_ext_s(ld, NULL, LDAP_SCOPE_BASE, "objectClass=*", attrs, false, NULL, NULL, &timeOut, LDAP_NO_LIMIT, &msg);
  207. if(err != LDAP_SUCCESS)
  208. {
  209. DBGLOG("ldap_search_ext_s error: %s", ldap_err2string( err ));
  210. if (msg)
  211. ldap_msgfree(msg);
  212. return err;
  213. }
  214. LDAPMessage* entry = ldap_first_entry(ld, msg);
  215. if(entry != NULL)
  216. {
  217. CLDAPGetValuesLenWrapper vals(ld, entry, "namingContexts");
  218. if(vals.hasValues())
  219. {
  220. int i = 0;
  221. const char* curdn;
  222. StringBuffer onedn;
  223. while((curdn = vals.queryCharValue(i)) != NULL)
  224. {
  225. if(*curdn != '\0' && (strncmp(curdn, "dc=", 3) == 0 || strncmp(curdn, "DC=", 3) == 0) && strstr(curdn,"DC=ForestDnsZones")==0 && strstr(curdn,"DC=DomainDnsZones")==0 )
  226. {
  227. if(domainDN.length() == 0)
  228. {
  229. StringBuffer curdomain;
  230. getName(curdn, curdomain);
  231. if(onedn.length() == 0)
  232. {
  233. DBGLOG("Queried '%s', selected basedn '%s'",curdn, curdomain.str());
  234. onedn.append(curdomain.str());
  235. }
  236. else
  237. DBGLOG("Ignoring %s", curdn);
  238. if(!domainname || !*domainname || stricmp(curdomain.str(), domainname) == 0)
  239. domainDN.append(curdn);
  240. }
  241. }
  242. else if(*curdn != '\0' && strcmp(curdn, "o=NetscapeRoot") == 0)
  243. {
  244. PROGLOG("Deduced LDAP Server Type 'iPlanet'");
  245. deducedSType = IPLANET;
  246. }
  247. i++;
  248. }
  249. if(domainDN.length() == 0)
  250. domainDN.append(onedn.str());
  251. if (deducedSType == LDAPSERVER_UNKNOWN)
  252. {
  253. if(i <= 1)
  254. {
  255. PROGLOG("Deduced LDAP Server Type 'OpenLDAP'");
  256. deducedSType = OPEN_LDAP;
  257. }
  258. else
  259. {
  260. PROGLOG("Deduced LDAP Server Type 'Active Directory'");
  261. deducedSType = ACTIVE_DIRECTORY;
  262. }
  263. }
  264. }
  265. }
  266. ldap_msgfree(msg);
  267. LDAP_UNBIND(ld);
  268. if (stype == LDAPSERVER_UNKNOWN)
  269. stype = deducedSType;
  270. else if (deducedSType != stype)
  271. WARNLOG("Ignoring deduced LDAP Server Type, does not match config LDAPServerType");
  272. return err;
  273. }
  274. void LdapUtils::bin2str(MemoryBuffer& from, StringBuffer& to)
  275. {
  276. const char* frombuf = from.toByteArray();
  277. char tmp[3];
  278. for(unsigned i = 0; i < from.length(); i++)
  279. {
  280. unsigned char c = frombuf[i];
  281. sprintf(tmp, "%02X", c);
  282. tmp[2] = 0;
  283. to.append("\\").append(tmp);
  284. }
  285. }
  286. void LdapUtils::normalizeDn(const char* dn, const char* basedn, StringBuffer& dnbuf)
  287. {
  288. dnbuf.clear();
  289. cleanupDn(dn, dnbuf);
  290. if(!containsBasedn(dnbuf.str()))
  291. dnbuf.append(",").append(basedn);
  292. }
  293. bool LdapUtils::containsBasedn(const char* str)
  294. {
  295. if(str == NULL || str[0] == '\0')
  296. return false;
  297. else
  298. return (strstr(str, "dc=") != NULL);
  299. }
  300. void LdapUtils::cleanupDn(const char* dn, StringBuffer& dnbuf)
  301. {
  302. if(dn == NULL || dn[0] == '\0')
  303. return;
  304. dnbuf.append(dn);
  305. dnbuf.toLowerCase();
  306. }
  307. bool LdapUtils::getDcName(const char* domain, StringBuffer& dc)
  308. {
  309. bool ret = false;
  310. #ifdef _WIN32
  311. PDOMAIN_CONTROLLER_INFO psInfo = NULL;
  312. DWORD dwErr = DsGetDcName(NULL, domain, NULL, NULL, DS_FORCE_REDISCOVERY | DS_DIRECTORY_SERVICE_REQUIRED, &psInfo);
  313. if( dwErr == NO_ERROR)
  314. {
  315. const char* dcname = psInfo->DomainControllerName;
  316. if(dcname != NULL)
  317. {
  318. while(*dcname == '\\')
  319. dcname++;
  320. dc.append(dcname);
  321. ret = true;
  322. }
  323. NetApiBufferFree(psInfo);
  324. }
  325. else
  326. {
  327. DBGLOG("Error getting domain controller, error = %d", dwErr);
  328. ret = false;
  329. }
  330. #endif
  331. return ret;
  332. }
  333. void LdapUtils::getName(const char* dn, StringBuffer& name)
  334. {
  335. const char* bptr = dn;
  336. while(*bptr != '\0' && *bptr != '=')
  337. bptr++;
  338. if(*bptr == '\0')
  339. {
  340. name.append(dn);
  341. return;
  342. }
  343. else
  344. bptr++;
  345. const char* colon = strstr(bptr, ",");
  346. if(colon == NULL)
  347. name.append(bptr);
  348. else
  349. name.append(colon - bptr, bptr);
  350. }