addn.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /** BEGIN COPYRIGHT BLOCK
  2. * Copyright (C) 2016 Red Hat, Inc.
  3. * All rights reserved.
  4. *
  5. * License: GPL (version 3 or any later version).
  6. * See LICENSE for details.
  7. * END COPYRIGHT BLOCK **/
  8. #ifdef HAVE_CONFIG_H
  9. # include <config.h>
  10. #endif
  11. /*
  12. * AD DN bind plugin
  13. *
  14. * This plugin allows violation of the LDAP v3 standard for binds to occur
  15. * as "uid" or "[email protected]". This plugin re-maps those terms to a real
  16. * dn such as uid=user,ou=People,dc=example,dc=com.
  17. */
  18. #include "addn.h"
  19. /* is there a better type we can use here? */
  20. #define ADDN_FAILURE 1
  21. static void* plugin_identity = NULL;
  22. static char *plugin_name = "addn_plugin";
  23. static Slapi_PluginDesc addn_description = {
  24. "addn",
  25. VENDOR,
  26. DS_PACKAGE_VERSION,
  27. "Allow AD DN style bind names to LDAP"
  28. };
  29. int
  30. addn_filter_validate(char *config_filter)
  31. {
  32. if (config_filter == NULL) {
  33. return 1;
  34. }
  35. char *lptr = NULL;
  36. char *rptr = NULL;
  37. lptr = PL_strstr(config_filter, "%s");
  38. rptr = PL_strrstr(config_filter, "%s");
  39. /* There is one instance, and one only! */
  40. if (lptr == rptr) {
  41. return LDAP_SUCCESS;
  42. }
  43. /* Is there a way to now compile and check the filter syntax? */
  44. return 1;
  45. }
  46. /* These will probably move to slapi_ hopefully, they make plugin writing easier ... */
  47. /* Get all the configs under cn=<name>,cn=plugins,cn=config */
  48. /* Slapi_Entry **
  49. addn_get_all_subconfigs(Slapi_PBlock *pb) */
  50. /* Filter configs? */
  51. /* Slapi_Entry **
  52. addn_filter_subconfigs(Slapi_PBlock *pb, Slapi_Entry * (*func)(Slapi_PBlock *, Slapi_Entry *) ) */
  53. /* Map on the configs? */
  54. /* Slapi_Entry **
  55. add_filter_subconfigs(Slapi_PBlock *pb, Slapi_Entry * (*func)(Slapi_PBlock *, Slapi_Entry *)) */
  56. /* Get a specific config under cn=<name>,cn=plugins,cn=config */
  57. Slapi_Entry *
  58. addn_get_subconfig(Slapi_PBlock *pb, char *identifier)
  59. {
  60. char *config_dn = NULL;
  61. char *filter = NULL;
  62. int search_result = 0;
  63. int entry_count = 0;
  64. Slapi_DN *config_sdn = NULL;
  65. Slapi_PBlock *search_pblock = NULL;
  66. Slapi_Entry *result = NULL;
  67. Slapi_Entry **entries = NULL;
  68. /* Get our config DN base. */
  69. slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_DN, &config_dn);
  70. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_get_subconfig: Getting config for %s\n", config_dn);
  71. config_sdn = slapi_sdn_new_dn_byval(config_dn);
  72. filter = slapi_ch_smprintf("(cn=%s)", identifier);
  73. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_get_subconfig: Searching with filter %s\n", filter);
  74. /* Search the backend, and apply our filter given the username */
  75. search_pblock = slapi_pblock_new();
  76. if (search_pblock == NULL) {
  77. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_get_subconfig: CRITICAL: Unable to allocate search_pblock!!!\n");
  78. goto out;
  79. }
  80. slapi_search_internal_set_pb_ext(search_pblock, config_sdn, LDAP_SCOPE_ONELEVEL,
  81. filter, NULL, 0 /* attrs only */,
  82. NULL /* controls */, NULL /* uniqueid */,
  83. plugin_identity, 0 /* actions */);
  84. slapi_search_internal_pb(search_pblock);
  85. search_result = slapi_pblock_get(search_pblock, SLAPI_PLUGIN_INTOP_RESULT, &search_result);
  86. if (search_result != LDAP_SUCCESS) {
  87. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_get_subconfig: CRITICAL: Internal search pblock get failed!!!\n");
  88. goto out;
  89. }
  90. if (search_result == LDAP_NO_SUCH_OBJECT) {
  91. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_get_subconfig: LDAP_NO_SUCH_OBJECT \n");
  92. goto out;
  93. }
  94. /* On all other errors, just fail out. */
  95. if (search_result != LDAP_SUCCESS) {
  96. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_get_subconfig: CRITICAL: Internal search error occured %d \n", search_result);
  97. goto out;
  98. }
  99. search_result = slapi_pblock_get(search_pblock, SLAPI_NENTRIES, &entry_count);
  100. if (search_result != LDAP_SUCCESS) {
  101. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_get_subconfig: CRITICAL: Unable to retrieve number of entries from pblock!\n");
  102. goto out;
  103. }
  104. /* If there are multiple results, we should also fail as we cannot */
  105. /* be sure of the real configuration we are about to use */
  106. if (entry_count != 1) {
  107. /* Is there a better log level for this? */
  108. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_get_subconfig: WARNING, multiple or no results returned. Failing to auth ...\n");
  109. goto out;
  110. }
  111. search_result = slapi_pblock_get(search_pblock, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
  112. if (search_result != LDAP_SUCCESS) {
  113. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_get_subconfig: CRITICAL: Unable to retrieve entries from pblock!\n");
  114. goto out;
  115. }
  116. result = slapi_entry_dup(entries[0]);
  117. out:
  118. if (search_pblock != NULL) {
  119. slapi_free_search_results_internal(search_pblock);
  120. slapi_pblock_destroy(search_pblock);
  121. }
  122. slapi_sdn_free(&config_sdn);
  123. slapi_ch_free_string(&filter);
  124. return result;
  125. }
  126. int
  127. addn_prebind(Slapi_PBlock *pb)
  128. {
  129. struct addn_config *config = NULL;
  130. Slapi_Entry *domain_config = NULL;
  131. Slapi_DN *pb_sdn_target = NULL;
  132. Slapi_DN *pb_sdn_mapped = NULL;
  133. char *dn = NULL;
  134. char *dn_bind = NULL;
  135. int dn_bind_len = 0;
  136. char *dn_bind_escaped = NULL;
  137. char *dn_domain = NULL;
  138. int dn_domain_len = 0;
  139. char *dn_domain_escaped = NULL;
  140. char *be_suffix = NULL;
  141. Slapi_DN *be_suffix_dn = NULL;
  142. char *config_filter = NULL;
  143. char *filter = NULL;
  144. int result = 0;
  145. static char *attrs[] = { "dn", NULL };
  146. Slapi_PBlock *search_pblock = NULL;
  147. int search_result = 0;
  148. Slapi_Entry **entries = NULL;
  149. int entry_count = 0;
  150. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: --> begin\n");
  151. slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &pb_sdn_target);
  152. /* We need to discard the const because later functions require char * only */
  153. dn = (char *)slapi_sdn_get_dn(pb_sdn_target);
  154. if (dn == NULL) {
  155. result = ADDN_FAILURE;
  156. goto out;
  157. }
  158. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Recieved %s\n", dn);
  159. result = slapi_dn_syntax_check(NULL, dn, 0);
  160. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Dn validation %d\n", result);
  161. if (result == LDAP_SUCCESS) {
  162. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Dn syntax is correct, do not alter\n");
  163. /* This is a syntax correct bind dn. Leave it alone! */
  164. goto out;
  165. }
  166. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Dn syntax is incorrect, it may need ADDN mangaling\n");
  167. result = slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &config);
  168. if (result != LDAP_SUCCESS || config == NULL) {
  169. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Unable to retrieve plugin configuration!\n");
  170. result = ADDN_FAILURE;
  171. goto out;
  172. }
  173. /* Right, the dn is invalid! This means it *could* be addn syntax */
  174. /* Do we have any other validation to run here? */
  175. /* Split the @domain component (if any) */
  176. dn_bind = strtok(dn, "@");
  177. /* Filter escape the name, and the domain parts */
  178. if (dn_bind != NULL) {
  179. dn_bind_len = strlen(dn_bind);
  180. dn_bind_escaped = slapi_escape_filter_value(dn_bind, dn_bind_len);
  181. dn_bind_len = strlen(dn_bind_escaped);
  182. }
  183. dn_domain = strtok(NULL, "@");
  184. /* You have a domain! Lets use it. */
  185. if (dn_domain != NULL) {
  186. dn_domain_len = strlen(dn_domain);
  187. dn_domain_escaped = slapi_escape_filter_value(dn_domain, dn_domain_len);
  188. dn_domain_len = strlen(dn_domain_escaped);
  189. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Selected bind submitted domain %s\n", dn_domain_escaped);
  190. } else {
  191. /* Or we could do the escape earlier. */
  192. dn_domain_escaped = slapi_ch_strdup(config->default_domain);
  193. dn_domain_len = config->default_domain_len;
  194. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Selected default domain %s\n", dn_domain_escaped);
  195. }
  196. /* If we don't have the @domain. get a default from config */
  197. if (dn_domain_escaped == NULL) {
  198. /* This could alternately be domain, then we do the same domain -> suffix conversion .... */
  199. /* Select the correct backend. If no backend, bail. */
  200. }
  201. /* Now get the config that matches. */
  202. domain_config = addn_get_subconfig(pb, dn_domain_escaped);
  203. if (domain_config == NULL) {
  204. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: no matching domain configuration for %s\n", dn_domain_escaped);
  205. result = ADDN_FAILURE;
  206. goto out;
  207. }
  208. /* Now we can get our suffix. */
  209. be_suffix = slapi_entry_attr_get_charptr(domain_config, "addn_base");
  210. be_suffix_dn = slapi_sdn_new_dn_byval(be_suffix);
  211. /* Get our filter. From the config */
  212. config_filter = slapi_entry_attr_get_charptr(domain_config, "addn_filter");
  213. /* Validate the filter_template only has one %s */
  214. if (addn_filter_validate(config_filter) != LDAP_SUCCESS) {
  215. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Failed to validate addn_filter %s for domain %s\n", config_filter, dn_domain_escaped);
  216. result = ADDN_FAILURE;
  217. goto out;
  218. }
  219. /* Put the search term into the filter. */
  220. /* Current design relies on there only being a single search term! */
  221. filter = slapi_ch_smprintf(config_filter, dn_bind_escaped);
  222. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: Searching with filter %s\n", filter);
  223. /* Search the backend, and apply our filter given the username */
  224. search_pblock = slapi_pblock_new();
  225. if (search_pblock == NULL) {
  226. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Unable to allocate search_pblock!!!\n");
  227. result = ADDN_FAILURE;
  228. goto out;
  229. }
  230. slapi_search_internal_set_pb_ext(search_pblock, be_suffix_dn, LDAP_SCOPE_SUBTREE,
  231. filter, attrs, 0 /* attrs only */,
  232. NULL /* controls */, NULL /* uniqueid */,
  233. plugin_identity, 0 /* actions */);
  234. slapi_search_internal_pb(search_pblock);
  235. result = slapi_pblock_get(search_pblock, SLAPI_PLUGIN_INTOP_RESULT, &search_result);
  236. if (result != LDAP_SUCCESS) {
  237. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Internal search pblock get failed!!!\n");
  238. result = ADDN_FAILURE;
  239. goto out;
  240. }
  241. /* No results are an error: Allow the plugin to continue, because */
  242. /* the bind will fail anyway ....? */
  243. /* Do we want to return a fail here? Or pass, and allow the auth code
  244. * to fail us. That will prevent some attacks I feel as this could be a
  245. * disclosure attack ....
  246. */
  247. if (search_result == LDAP_NO_SUCH_OBJECT) {
  248. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: LDAP_NO_SUCH_OBJECT \n");
  249. result = LDAP_SUCCESS;
  250. goto out;
  251. }
  252. /* On all other errors, just fail out. */
  253. if (search_result != LDAP_SUCCESS) {
  254. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: CRITICAL: Internal search error occured %d \n", search_result);
  255. result = ADDN_FAILURE;
  256. goto out;
  257. }
  258. result = slapi_pblock_get(search_pblock, SLAPI_NENTRIES, &entry_count);
  259. if (result != LDAP_SUCCESS) {
  260. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Unable to retrieve number of entries from pblock!\n");
  261. result = ADDN_FAILURE;
  262. goto out;
  263. }
  264. /* If there are multiple results, we should also fail as we cannot */
  265. /* be sure of the real object we want to bind to */
  266. if (entry_count > 1) {
  267. /* Is there a better log level for this? */
  268. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: WARNING, multiple results returned. Failing to auth ...\n");
  269. result = ADDN_FAILURE;
  270. goto out;
  271. }
  272. result = slapi_pblock_get(search_pblock, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
  273. if (result != LDAP_SUCCESS) {
  274. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Unable to retrieve entries from pblock!\n");
  275. result = ADDN_FAILURE;
  276. goto out;
  277. }
  278. /* We should only have one entry here! */
  279. pb_sdn_mapped = slapi_sdn_dup(slapi_entry_get_sdn(*entries));
  280. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: SEARCH entry dn=%s is mapped from addn=%s\n", slapi_sdn_get_dn(pb_sdn_mapped), dn);
  281. /* If there is a result, take the DN and make the SDN for PB */
  282. /* Free the original SDN */
  283. result = slapi_pblock_set(pb, SLAPI_TARGET_SDN, pb_sdn_mapped);
  284. if (result != LDAP_SUCCESS) {
  285. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_prebind: CRITICAL: Unable to set new mapped DN to pblock!\n");
  286. /* We have to free the mapped SDN here */
  287. slapi_sdn_free(&pb_sdn_mapped);
  288. result = ADDN_FAILURE;
  289. goto out;
  290. }
  291. slapi_sdn_free(&pb_sdn_target);
  292. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_prebind: <-- complete\n");
  293. result = LDAP_SUCCESS;
  294. out:
  295. if (search_pblock != NULL) {
  296. slapi_free_search_results_internal(search_pblock);
  297. slapi_pblock_destroy(search_pblock);
  298. }
  299. slapi_entry_free(domain_config);
  300. slapi_sdn_free(&be_suffix_dn);
  301. slapi_ch_free_string(&be_suffix);
  302. slapi_ch_free_string(&dn_bind_escaped);
  303. slapi_ch_free_string(&dn_domain_escaped);
  304. slapi_ch_free_string(&filter);
  305. return result;
  306. }
  307. /*
  308. * addn_start
  309. *
  310. * This is called when the plugin is started. It allows a configuration change
  311. * To be made while the directory server is live.
  312. */
  313. int
  314. addn_start(Slapi_PBlock *pb)
  315. {
  316. Slapi_Entry *plugin_entry = NULL;
  317. struct addn_config *config = NULL;
  318. char *domain = NULL;
  319. int result = LDAP_SUCCESS;
  320. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_start: starting ...\n");
  321. /* It looks like we mis-use the SLAPI_ADD_ENTRY space in PB during plugin startup */
  322. result = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &plugin_entry);
  323. if (result != LDAP_SUCCESS || plugin_entry == NULL) {
  324. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_start: CRITICAL: Failed to retrieve config entry!\n");
  325. return SLAPI_PLUGIN_FAILURE;
  326. }
  327. /* Now set the values into the config */
  328. /* Probably need to get the config DN we are using too. */
  329. /* Are there some error cases here we should be checking? */
  330. config = (struct addn_config *)slapi_ch_malloc(sizeof(struct addn_config));
  331. domain = slapi_entry_attr_get_charptr(plugin_entry, "addn_default_domain");
  332. if (domain == NULL) {
  333. slapi_log_err(SLAPI_LOG_ERR, plugin_name, "addn_start: CRITICAL: No default domain in configuration, you must set addn_default_domain!\n");
  334. return SLAPI_PLUGIN_FAILURE;
  335. }
  336. config->default_domain = slapi_escape_filter_value(domain, strlen(domain));
  337. config->default_domain_len = strlen(config->default_domain);
  338. /* Set into the pblock */
  339. slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, (void *) config);
  340. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_start: startup complete\n");
  341. return LDAP_SUCCESS;
  342. }
  343. /*
  344. * addn_close
  345. *
  346. * Called when the plugin is stopped. Frees the configs as needed
  347. */
  348. int
  349. addn_close(Slapi_PBlock *pb)
  350. {
  351. struct addn_config *config = NULL;
  352. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_close: stopping ...\n");
  353. slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &config);
  354. if (config != NULL) {
  355. slapi_ch_free_string(&config->default_domain);
  356. slapi_ch_free((void **) &config);
  357. slapi_pblock_set(pb, SLAPI_PLUGIN_PRIVATE, NULL);
  358. }
  359. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_close: stop complete\n");
  360. return LDAP_SUCCESS;
  361. }
  362. /*
  363. * addn_init
  364. *
  365. * Initialise and register the addn plugin to the directory server.
  366. */
  367. int
  368. addn_init(Slapi_PBlock *pb)
  369. {
  370. int result = 0;
  371. result = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03);
  372. if (result != LDAP_SUCCESS) {
  373. goto out;
  374. }
  375. /* Get and stash our plugin identity */
  376. slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity);
  377. result = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void*)&addn_description);
  378. if (result != LDAP_SUCCESS) {
  379. goto out;
  380. }
  381. result = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void*)addn_start);
  382. if (result != LDAP_SUCCESS) {
  383. goto out;
  384. }
  385. result = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void*)addn_close);
  386. if (result != LDAP_SUCCESS) {
  387. goto out;
  388. }
  389. result = slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void*)addn_prebind);
  390. if (result != LDAP_SUCCESS) {
  391. goto out;
  392. }
  393. out:
  394. if (result == LDAP_SUCCESS) {
  395. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_init: Success: plugin loaded.\n");
  396. slapi_log_err(SLAPI_LOG_WARNING, plugin_name, "addn_init: WARNING: The use of this plugin violates the LDAPv3 specification RFC4511 section 4.2 BindDN specification. You have been warned ...\n");
  397. } else {
  398. slapi_log_err(SLAPI_LOG_PLUGIN, plugin_name, "addn_init: Error: %d. \n", result);
  399. }
  400. return result;
  401. }