referral.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  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) 2001 Sun Microsystems, Inc. Used by permission.
  35. * Copyright (C) 2005 Red Hat, Inc.
  36. * All rights reserved.
  37. * END COPYRIGHT BLOCK **/
  38. /*
  39. * referrals.c - LDAP referral-related routines.
  40. * a) manage in-memory copiedfrom and copyingfrom referrals.
  41. * b) function to adjust smart referrals to match operation parameters.
  42. */
  43. #include <stdio.h>
  44. #include "slap.h"
  45. /* Forward Decls */
  46. static void adjust_referral_basedn( char **urlp, const Slapi_DN *refcontainerdn, char *opdn_norm, int isreference );
  47. static int dn_is_below( const char *dn_norm, const char *ancestor_norm );
  48. static Ref_Array *g_get_global_referrals(void);
  49. static void ref_free (Ref **goner);
  50. static Ref_Array global_referrals;
  51. #define SLAPD_DEFAULT_REFARRAY_SIZE 10
  52. /*
  53. * Function: g_get_global_referrals
  54. *
  55. * Returns: nothing
  56. *
  57. * Description: Fills the global_referrals referral array.
  58. * The size is set to "size". The mutex is created.
  59. * The array is calloc'd.
  60. */
  61. static Ref_Array *
  62. g_get_global_referrals(void)
  63. {
  64. if (global_referrals.ra_rwlock == NULL)
  65. {
  66. /* Make a new lock */
  67. global_referrals.ra_rwlock = rwl_new();
  68. if (global_referrals.ra_rwlock == NULL) {
  69. LDAPDebug( LDAP_DEBUG_ANY,
  70. "ref_array_init: new lock creation failed\n", 0, 0, 0);
  71. exit (-1);
  72. }
  73. /* Initialize all the fields. */
  74. global_referrals.ra_size = SLAPD_DEFAULT_REFARRAY_SIZE;
  75. global_referrals.ra_nextindex = 0;
  76. global_referrals.ra_readcount = 0;
  77. global_referrals.ra_refs = (Ref **) slapi_ch_calloc(SLAPD_DEFAULT_REFARRAY_SIZE, sizeof( Ref * ));
  78. }
  79. return( &global_referrals );
  80. }
  81. /*
  82. * Function: ref_free
  83. *
  84. * Returns: nothing
  85. *
  86. * Description: frees up "goner"
  87. *
  88. * Author: RJP
  89. *
  90. */
  91. static void
  92. ref_free (Ref **goner)
  93. {
  94. slapi_ch_free((void**) &((*goner)->ref_dn));
  95. ber_bvfree( (*goner)->ref_referral );
  96. slapi_ch_free((void**) goner);
  97. }
  98. /*
  99. * Function: referrals_free
  100. *
  101. * Returns: nothing
  102. *
  103. * Description: frees up everything.
  104. *
  105. * Author: RJP
  106. *
  107. */
  108. void
  109. referrals_free (void)
  110. {
  111. int walker;
  112. Ref_Array *grefs = NULL;
  113. grefs = g_get_global_referrals();
  114. GR_LOCK_WRITE();
  115. /* Walk down the array, deleting each referral */
  116. for (walker = 0; walker < grefs->ra_nextindex; walker++){
  117. ref_free (&grefs->ra_refs[walker]);
  118. }
  119. slapi_ch_free ((void **) &grefs->ra_refs);
  120. GR_UNLOCK_WRITE();
  121. rwl_free( &grefs->ra_rwlock );
  122. }
  123. /*
  124. * ref_adjust() -- adjust referrals based on operation-specific data.
  125. * The general idea is for us (the server) to be smart so LDAP clients
  126. * can be as dumb as possible.
  127. *
  128. * XXXmcs: We always duplicate the referrals even if no adjustments need to
  129. * be made. It would be more efficient but more complicated to avoid making
  130. * a copy if we don't need to change anything. If that were done, the
  131. * interface to this function would need to change since the caller would
  132. * need to know whether the returned memory needed to be freed or not.
  133. *
  134. * Parameters:
  135. * pb is the pblock for the operation we are working on.
  136. * urls is a list of referrals.
  137. * refsdn is the Slapi_DN of the entry where the referral is stored.
  138. * is_reference is true if a searchResultReference is being returned.
  139. *
  140. * Returns:
  141. * a (possibly modified) copy of the urls. It should be freed by
  142. * calling ber_bvecfree().
  143. *
  144. *
  145. * First we strip off any labels from the referral URLs. For example, a
  146. * referral like this:
  147. *
  148. * ldap://directory.airius.com/ou=people,o=airius.com Ref to people tree
  149. *
  150. * is changed to:
  151. *
  152. * ldap://directory.airius.com/ou=people,o=airius.com
  153. *
  154. * Next, if the referral includes a baseDN we potentially modify it or strip
  155. * it off entirely. If the referral doesn't include a baseDN and we're
  156. * processing a continuation reference, we add a baseDN.
  157. *
  158. * Finally, if we are processing a continuation reference that resulted from
  159. * a one-level search, we append "??base" to the referral so the client will
  160. * use the correct scope when chasing the reference.
  161. */
  162. struct berval **
  163. ref_adjust( Slapi_PBlock *pb, struct berval **urls, const Slapi_DN *refsdn,
  164. int is_reference )
  165. {
  166. int i, len, scope;
  167. char *p, *opdn_norm;
  168. struct berval **urlscopy;
  169. Operation *op;
  170. if ( NULL == urls || NULL == urls[0] ) {
  171. return( NULL );
  172. }
  173. PR_ASSERT( pb != NULL );
  174. /*
  175. * grab the operation target DN and operation structure.
  176. * if the operation is a search, get the scope as well.
  177. */
  178. if ( slapi_pblock_get( pb, SLAPI_TARGET_DN, &p ) != 0 || p == NULL ||
  179. slapi_pblock_get( pb, SLAPI_OPERATION, &op ) != 0 || op == NULL ||
  180. ( operation_get_type(op) == SLAPI_OPERATION_SEARCH && slapi_pblock_get( pb,
  181. SLAPI_SEARCH_SCOPE, &scope ) != 0 )) {
  182. LDAPDebug( LDAP_DEBUG_ANY, "ref_adjust: referrals suppressed "
  183. "(could not get target DN, operation, or scope from pblock)\n",
  184. 0, 0, 0 );
  185. return( NULL );
  186. }
  187. /*
  188. * normalize the DNs we plan to compare with others later.
  189. */
  190. opdn_norm = slapi_dn_normalize( slapi_ch_strdup( p ));
  191. /*
  192. * count referrals and duplicate the array
  193. */
  194. for ( i = 0; urls[i] != NULL; ++i ) {
  195. ;
  196. }
  197. urlscopy = (struct berval **) slapi_ch_malloc(( i + 1 ) *
  198. sizeof( struct berval * ));
  199. for ( i = 0; urls[i] != NULL; ++i ) {
  200. /*
  201. * duplicate the URL, stripping off the label if there is one and
  202. * leaving extra room for "??base" in case we need to append that.
  203. */
  204. urlscopy[i] = (struct berval *)slapi_ch_malloc(
  205. sizeof( struct berval ));
  206. if (( p = strchr( urls[i]->bv_val, ' ' )) == NULL ) {
  207. len = strlen( urls[i]->bv_val );
  208. } else {
  209. len = p - urls[i]->bv_val;
  210. }
  211. urlscopy[i]->bv_val = (char *)slapi_ch_malloc( len + 7 );
  212. memcpy( urlscopy[i]->bv_val, urls[i]->bv_val, len );
  213. urlscopy[i]->bv_val[len] = '\0';
  214. /*
  215. * adjust the baseDN as needed and set the length
  216. */
  217. adjust_referral_basedn( &urlscopy[i]->bv_val, refsdn,
  218. opdn_norm, is_reference );
  219. urlscopy[i]->bv_len = strlen( urlscopy[i]->bv_val );
  220. /*
  221. * if we are dealing with a continuation reference that resulted
  222. * from a one-level search, add a scope of base to the URL.
  223. */
  224. if ( is_reference && operation_get_type(op) == SLAPI_OPERATION_SEARCH &&
  225. scope == LDAP_SCOPE_ONELEVEL ) {
  226. strcat( urlscopy[i]->bv_val, "??base" );
  227. urlscopy[i]->bv_len += 6;
  228. }
  229. }
  230. urlscopy[i] = NULL; /* NULL terminate the new referrals array */
  231. /*
  232. * log what we did (for debugging purposes)
  233. */
  234. if ( LDAPDebugLevelIsSet( LDAP_DEBUG_ARGS )) {
  235. for ( i = 0; urlscopy[i] != NULL; ++i ) {
  236. LDAPDebug( LDAP_DEBUG_ARGS, "ref_adjust: \"%s\" -> \"%s\"\n",
  237. urls[i]->bv_val, urlscopy[i]->bv_val, 0 );
  238. }
  239. }
  240. /*
  241. * done -- clean up and return the new referrals array
  242. */
  243. slapi_ch_free( (void **)&opdn_norm );
  244. return( urlscopy );
  245. }
  246. /*
  247. * adjust_referral_basedn() -- pull the referral apart and modify the
  248. * baseDN contained in the referral if appropriate not.
  249. * If the referral does not have baseDN and we're not processing a
  250. * continuation reference, we do not need to do anything. If we're
  251. * processing a continuation reference, we need to add a baseDN.
  252. *
  253. * If it does have one, there are two special cases we deal with:
  254. *
  255. * 1) The referral's baseDN is an ancestor of the operation's baseDN,
  256. * which means that the LDAP client already has a more specific
  257. * baseDN than is contained in the referral. If so, we strip off
  258. * the baseDN so the client will re-use its operation DN when
  259. * chasing the referral.
  260. *
  261. * 2) The referral's baseDN is not an ancestor of the operation's
  262. * baseDN and the DN of the entry that contains the referral is
  263. * an ancestor of the operation DN. If so, we remove the portion
  264. * of the operation DN that matches the DN of the entry containing
  265. * the referral and prepend what's left to the referral base DN.
  266. *
  267. * We assume that the baseDN is the right-most piece of the URL, i.e., there
  268. * is no attribute list, scope, or options after the baseDN.
  269. *
  270. * The code also assumes that the baseDN doesn't contain any '/' character.
  271. * This later assumption NEED to be removed and code changed appropriately.
  272. */
  273. static void
  274. adjust_referral_basedn( char **urlp, const Slapi_DN *refsdn,
  275. char *opdn_norm, int isreference )
  276. {
  277. LDAPURLDesc *ludp = NULL;
  278. char *p, *refdn_norm;
  279. int rc = 0;
  280. PR_ASSERT( urlp != NULL );
  281. PR_ASSERT( *urlp != NULL );
  282. PR_ASSERT( refsdn != NULL );
  283. PR_ASSERT( opdn_norm != NULL );
  284. if (opdn_norm == NULL){
  285. /* Be safe but this should never ever happen */
  286. return;
  287. }
  288. rc = ldap_url_parse( *urlp, &ludp );
  289. if ((rc != 0) &&
  290. (rc != LDAP_URL_ERR_NODN))
  291. /*
  292. * rc != LDAP_URL_ERR_NODN is to circumvent a pb in the C-SDK
  293. * in ldap_url_parse. The function will return an error if the
  294. * URL contains no DN (though it is a valid URL according to
  295. * RFC 2255.
  296. */
  297. {
  298. /* Nothing to do, just return */
  299. return;
  300. }
  301. if (ludp && (ludp->lud_dn != NULL)) {
  302. refdn_norm = slapi_dn_normalize( slapi_ch_strdup( ludp->lud_dn ));
  303. if ( dn_is_below( opdn_norm, refdn_norm )) {
  304. /*
  305. * Strip off the baseDN.
  306. */
  307. if (( p = strrchr( *urlp, '/' )) != NULL ) {
  308. *p = '\0';
  309. }
  310. } else if ( dn_is_below( opdn_norm, slapi_sdn_get_ndn(refsdn) )) {
  311. int cur_len, add_len;
  312. /*
  313. * Prepend the portion of the operation DN that does not match
  314. * the ref container DN to the referral baseDN.
  315. */
  316. add_len = strlen( opdn_norm ) - strlen( slapi_sdn_get_ndn(refsdn) );
  317. cur_len = strlen( *urlp );
  318. /* + 7 because we keep extra space in case we add ??base */
  319. *urlp = slapi_ch_realloc( *urlp, cur_len + add_len + 7 );
  320. if (( p = strrchr( *urlp, '/' )) != NULL ) {
  321. ++p;
  322. memmove( p + add_len, p, strlen( p ) + 1 );
  323. memmove( p, opdn_norm, add_len );
  324. }
  325. } /* else: leave the original referral baseDN intact. */
  326. slapi_ch_free( (void **)&refdn_norm );
  327. } else if (isreference) {
  328. int cur_len, add_len;
  329. /* If ludp->lud_dn == NULL and isreference : Add the DN of the ref entry*/
  330. if ( dn_is_below( opdn_norm, slapi_sdn_get_ndn(refsdn) )){
  331. add_len = strlen(opdn_norm);
  332. p = opdn_norm;
  333. } else {
  334. add_len = strlen(slapi_sdn_get_ndn(refsdn));
  335. p = (char *)slapi_sdn_get_ndn(refsdn);
  336. }
  337. cur_len = strlen( *urlp );
  338. /* + 8 because we keep extra space in case we add a / and/or ??base */
  339. *urlp = slapi_ch_realloc( *urlp, cur_len + add_len + 8 );
  340. if ((*urlp)[cur_len - 1] != '/'){
  341. /* The URL is not ending with a /, let's add one */
  342. strcat(*urlp, "/");
  343. }
  344. strcat(*urlp, p);
  345. }
  346. if ( ludp != NULL ) {
  347. ldap_free_urldesc( ludp );
  348. }
  349. return;
  350. }
  351. /*
  352. * dn_is_below(): return non-zero if "dn_norm" is below "ancestor_norm" in
  353. * the DIT and return zero if not.
  354. */
  355. static int
  356. dn_is_below( const char *dn_norm, const char *ancestor_norm )
  357. {
  358. PR_ASSERT( dn_norm != NULL );
  359. PR_ASSERT( ancestor_norm != NULL );
  360. if ( 0 == strcasecmp( dn_norm, ancestor_norm )) {
  361. return( 0 ); /* same DN --> not an ancestor relationship */
  362. }
  363. return( slapi_dn_issuffix( dn_norm, ancestor_norm ));
  364. }
  365. /*
  366. * This function is useful to discover the source of data and provide
  367. * this as a referral. It is also useful if someone simply wants
  368. * to know if a dn is mastered somewhere else.
  369. *
  370. * For a given dn, traverse the referral list and look for the copiedFrom
  371. * attribute. If such an attribute is found get the server hostname
  372. * and port in the form "ldap://hostname:port".
  373. * Else return NULL.
  374. *
  375. * ORC extension: if orc == 1, this function will search for copyingFrom
  376. * which will refer searches and compares on trees that are half-baked.
  377. *
  378. * ORC extension: if cf_refs is NULL, then global_referrals is consulted,
  379. * otherwise, cf_refs is consulted.
  380. */
  381. struct berval **
  382. get_data_source(Slapi_PBlock *pb, const Slapi_DN *sdn, int orc, void *cfrp)
  383. {
  384. int walker;
  385. struct berval **bvp;
  386. struct berval *bv;
  387. int found_it;
  388. Ref_Array *grefs = NULL;
  389. Ref_Array *the_refs = NULL;
  390. Ref_Array *cf_refs = (Ref_Array *)cfrp;
  391. /* If no Ref_Array is given, use global_referrals */
  392. if (cf_refs == NULL) {
  393. grefs = g_get_global_referrals();
  394. the_refs = grefs;
  395. GR_LOCK_READ();
  396. } else {
  397. the_refs = cf_refs;
  398. }
  399. /* optimization: if orc is 1 (a read), then check the readcount*/
  400. if (orc && the_refs->ra_readcount == 0) {
  401. if (cf_refs == NULL) {
  402. GR_UNLOCK_READ();
  403. }
  404. return (NULL);
  405. }
  406. bvp = NULL;
  407. bv = NULL;
  408. found_it = 0;
  409. /* Walk down the array, testing each dn to make see if it's a parent of "dn" */
  410. for (walker = 0; walker < the_refs->ra_nextindex; walker++){
  411. if ( slapi_dn_issuffix(slapi_sdn_get_ndn(sdn), the_refs->ra_refs[walker]->ref_dn)) {
  412. found_it = 1;
  413. break;
  414. }
  415. }
  416. /* no referral, so return NULL */
  417. if (!found_it) {
  418. if (cf_refs == NULL) {
  419. GR_UNLOCK_READ();
  420. }
  421. return (NULL);
  422. }
  423. /*
  424. * Gotta make sure we're returning the right one. If orc is 1, then
  425. * only return a read referral. if orc is 0, then only return a
  426. * write referral.
  427. */
  428. if (orc && the_refs->ra_refs[walker]->ref_reads != 1) {
  429. if (cf_refs == NULL) {
  430. GR_UNLOCK_READ();
  431. }
  432. return (NULL);
  433. }
  434. if (!orc && the_refs->ra_refs[walker]->ref_writes != 1) {
  435. if (cf_refs == NULL) {
  436. GR_UNLOCK_READ();
  437. }
  438. return (NULL);
  439. }
  440. /* Fix for 310968 --- return an SSL referral to an SSL client */
  441. if ( 0 != ( pb->pb_conn->c_flags & CONN_FLAG_SSL )) {
  442. /* SSL connection */
  443. char * old_referral_string = NULL;
  444. char * new_referral_string = NULL;
  445. char *p = NULL;
  446. /* Get the basic referral */
  447. bv = slapi_ch_bvdup(the_refs->ra_refs[walker]->ref_referral);
  448. old_referral_string = bv->bv_val;
  449. /* Re-write it to replace ldap with ldaps, and remove the port information */
  450. /* The original string will look like this: ldap://host:port */
  451. /* We need to make it look like this: ldaps://host */
  452. /* Potentially the ":port" part might be missing from the original */
  453. new_referral_string = slapi_ch_smprintf("%s%s" , LDAPS_URL_PREFIX, old_referral_string + strlen(LDAP_URL_PREFIX) );
  454. /* Go looking for the port */
  455. p = new_referral_string + (strlen(LDAPS_URL_PREFIX) + 1);
  456. while (*p != '\0' && *p != ':') p++;
  457. if (':' == *p) {
  458. /* It had a port, zap it */
  459. *p = '\0';
  460. }
  461. /* Fix the bv to point to this new string */
  462. bv->bv_val = new_referral_string;
  463. /* Fix its length */
  464. bv->bv_len = strlen(bv->bv_val);
  465. /* Free the copy we made of the original */
  466. slapi_ch_free((void**)&old_referral_string);
  467. } else {
  468. /* regular connection */
  469. bv = (struct berval *) slapi_ch_bvdup(the_refs->ra_refs[walker]->ref_referral);
  470. }
  471. /* Package it up and send that puppy. */
  472. bvp = (struct berval **) slapi_ch_malloc( 2 * sizeof(struct berval *) );
  473. bvp[0] = bv;
  474. bvp[1] = NULL;
  475. if (cf_refs == NULL) {
  476. GR_UNLOCK_READ();
  477. }
  478. return(bvp);
  479. }