Re: pcache LDAP_MATCHING_RULE_IN_CHAIN support

Howard Chu <[email protected]> Fri, 10 Feb 2023 11:22:24 +0000
Newsgroups gmane.network.openldap.devel
Message-ID <[email protected]>
Howard Chu wrote:
> Johan wrote:
>> Hello all,
> 
>> We have an OpenLDAP instance proxying an active directory with back_meta and> mr_passthru.
>> We also have pcache on top, and as it do not support
>> LDAP_MATCHING_RULE_IN_CHAIN, I looked about implementing it.
> 
>> I found that we could retrieve cached results with pcacheQueryID, but I can't
>> get why this is not used in the current code?
> 
> It is of course used in the current code, for refreshing and expiring queries.

>> Plesae see attached patch

Please read https://openldap.org/devel/programming.html

> --- servers/slapd/overlays/pcache.c.ori	2023-02-04 10:56:07.910235675 +0100
> +++ servers/slapd/overlays/pcache.c	2023-02-04 10:57:14.319386931 +0100
> @@ -53,6 +53,16 @@
>   */
>  #define PCACHE_MONITOR
>  
> +// Not cacheable reasons
> +#define PCACHEMAXQUERIES_REACHED	0x1
> +#define PCACHEATTRSONLY				0x2
> +#define PCACHENOTEMPLATE			0x4
> +#define PCACHENOATTRSET				0x8

These values should be aligned. OpenLDAP source uses 4-column hard tabs for indents.
> +
> +// https://ldapwiki.com/wiki/LDAP_MATCHING_RULE_IN_CHAIN

This URL is currently unreachable. Probably should link to MSDN instead.

> +#define LDAP_MATCHING_RULE_IN_CHAIN "1.2.840.113556.1.4.1941"
> +
> +
>  /* query cache structs */
>  /* query */
>  
> @@ -512,7 +522,7 @@
>  	int attr_cnt;
>  	int t_cnt = 0;
>  	struct berval bv;
> -	char *p1, *p2;
> +	char *p1, *p2, *p3;
>  	AttributeDescription *ad;
>  	AttributeName *attrs;
>  
> @@ -532,9 +542,18 @@
>  		p2 = strchr( p1, '=' );
>  		if ( !p2 )
>  			break;
> -		if ( p2[-1] == '<' || p2[-1] == '>' ) p2--;
> -		bv.bv_val = p1;
> -		bv.bv_len = p2 - p1;
> +		// Incase of extended operation
> +		p3 = strchr( p1, ':' );
> +		if ( p3 && p3 < p2 ) {
> +			// FIXME: Is this valid syntax : "member:1.2.840.113556.1.4.1941:>=3" ?

Read RFC4515.

> +			if ( p3[-1] == '<' || p3[-1] == '>' ) p3--;
> +			bv.bv_val = p1;
> +			bv.bv_len = p3 - p1;
> +		} else {
> +			if ( p2[-1] == '<' || p2[-1] == '>' ) p2--;
> +			bv.bv_val = p1;
> +			bv.bv_len = p2 - p1;
> +		}
>  		ad = NULL;
>  		i = slap_bv2ad( &bv, &ad, text );
>  		if ( i ) {
> @@ -1136,7 +1155,6 @@
>  	return ret;
>  }
>  
> -
>  static struct berval*
>  merge_init_final(Operation *op, struct berval* init, struct berval* any,
>  	struct berval* final)
> @@ -1342,8 +1360,13 @@
>  			case LDAP_FILTER_LE:
>  				mrule = fs->f_ava->aa_desc->ad_type->sat_ordering;
>  				break;
> +			case LDAP_FILTER_EXT:
> +				if (0 == strncmp(fi->f_mr_rule_text.bv_val, LDAP_MATCHING_RULE_IN_CHAIN, fi->f_mr_rule_text.bv_len)) {

Probably should define the OID in a berval and use ber_bvcmp. We don't use "if (0 == xx)" we use "if (!xx)".

> +					mrule = fs->f_ava->aa_desc->ad_type->sat_equality;
> +				}
> +				break;
>  			default:
> -				mrule = NULL; 
> +				mrule = NULL;
>  			}
>  			if (mrule) {
>  				const char *text;
> @@ -1394,6 +1417,7 @@
>  				fi=fi->f_next;
>  				break;
>  			case LDAP_FILTER_EQUALITY:
> +			case LDAP_FILTER_EXT:
>  				if (ret == 0)
>  					res = 1;
>  				fs=fs->f_next;
> @@ -1941,6 +1965,19 @@
>  		fstr->bv_len += len;
>  		break;
>  
> +	case LDAP_FILTER_EXT:
> +		// Concatenate attribute name
> +		if ( f->f_mr_desc ) {
> +			ad = f->f_mr_desc;
> +			len = STRLENOF( "(::=)" ) + ad->ad_cname.bv_len + f->f_mr_rule_text.bv_len;
> +			ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s:%s:=)", ad->ad_cname.bv_val, f->f_mr_rule_text.bv_val );
> +			assert( ret == len );
> +			fstr->bv_len += len;
> +		} else {
> +			return -1;
> +		}

Again, read RFC4515.

> +		break;
> +
>  	case LDAP_FILTER_AND:
>  	case LDAP_FILTER_OR:
>  	case LDAP_FILTER_NOT: {
> @@ -2955,17 +2992,22 @@
>  {
>  	slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
>  	cache_manager *cm = on->on_bi.bi_private;
> -	query_manager*		qm = cm->qm;
> +	query_manager*	qm = cm->qm;

Don't make gratuitous whitespace changes.
>
>  	int i = -1;
>  
>  	Query		query;
>  	QueryTemplate	*qtemp = NULL;
> -	bindinfo *pbi = NULL;
> +	bindinfo	*pbi = NULL;
>  
> -	int 		attr_set = -1;
> -	CachedQuery 	*answerable = NULL;
> -	int 		cacheable = 0;
> +	int			attr_set = -1;
> +	CachedQuery	*answerable = NULL;
> +	int			cacheable = 0;
> +
> +	char	*fstr;
> +	Filter	*f;
> +
> +	int ncachereason = 0;
>  
>  	struct berval	tempstr;
>  
> @@ -3051,6 +3093,10 @@
>  				if (answerable)
>  					break;
>  			}
> +			if (cacheable == 0)
> +				ncachereason |= PCACHENOTEMPLATE;
> +		} else {
> +			ncachereason |= PCACHENOATTRSET;
>  		}
>  		op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
>  	}
> @@ -3098,7 +3144,16 @@
>  					}
>  				}
>  			}
> +
> +			/* Replace original query with query to cache ID */
> +			fstr = op->o_tmpalloc( sizeof("(pcacheQueryID=12345678-abcd-1234-abcd-123456789abc)")+1, op->o_tmpmemctx );
> +			sprintf(fstr, "(pcacheQueryID=%s)", answerable->q_uuid.bv_val);
> +			Debug( pcache_debug, "Replace search filter with %s\n", fstr, 0, 0 );

No. The fact the query is answerable doesn't mean you can replace the user's filter.
The user's filter may be more specific than the cached set, and will only return a
subset of entries. This code will break such queries.

> +
> +			f = str2filter(fstr);
> +			op->oq_search.rs_filter = f;
>  			i = cm->db.bd_info->bi_op_search( op, rs );
> +			op->o_tmpfree( fstr, op->o_tmpmemctx );
>  		}
>  		ldap_pvt_thread_rdwr_wunlock(&answerable->rwlock);
>  		/* locked by qtemp->qcfunc (query_containment) */
> @@ -3112,11 +3167,14 @@
>  	ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
>  	if (cm->num_cached_queries >= cm->max_queries) {
>  		cacheable = 0;
> +		ncachereason |= PCACHEMAXQUERIES_REACHED;
>  	}
>  	ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
>  
> -	if (op->ors_attrsonly)
> +	if (op->ors_attrsonly) {
>  		cacheable = 0;
> +		ncachereason |= PCACHEATTRSONLY;
> +	}
>  
>  	if (cacheable) {
>  		slap_callback		*cb;
> @@ -3170,7 +3228,27 @@
>  		}
>  
>  	} else {
> -		Debug( pcache_debug, "QUERY NOT CACHEABLE\n" );
> +		switch (ncachereason) {

You defined the ncachereason as a set of bitflags. You can't use a switch() statement here, it
will just hit default if more than 1 bit is set, which defeats the purpose of tracking any reasons.

> +		case PCACHEMAXQUERIES_REACHED:
> +			Debug( pcache_debug, "QUERY NOT CACHEABLE (max_entries reached)\n",
> + 					0, 0, 0);
> +			break;
> +		case PCACHEATTRSONLY:
> +			Debug( pcache_debug, "QUERY NOT CACHEABLE (attrs only)\n",
> +					0, 0, 0);
> +			break;
> +		case PCACHENOTEMPLATE:
> +			Debug( pcache_debug, "QUERY NOT CACHEABLE (No template)\n",
> +					0, 0, 0);
> +			break;
> +		case PCACHENOATTRSET:
> +			Debug( pcache_debug, "QUERY NOT CACHEABLE (No Attrset)\n",
> +					0, 0, 0);
> +			break;
> +		default:
> +			Debug( pcache_debug, "QUERY NOT CACHEABLE (unkown reason)\n",
> +					0, 0, 0);
> +		}
>  	}
>  
>  	return SLAP_CB_CONTINUE;

-- 
  -- Howard Chu
  CTO, Symas Corp.           http://www.symas.com
  Director, Highland Sun     http://highlandsun.com/hyc/
  Chief Architect, OpenLDAP  http://www.openldap.org/project/