ldaputils.cpp 13 KB

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