+
+#if SASL_VERSION_MAJOR >= 2
+static const char *slap_propnames[] = {
+ "*slapConn", "*authcDN", "*authzDN", NULL };
+
+static Filter generic_filter = { LDAP_FILTER_PRESENT };
+static struct berval generic_filterstr = BER_BVC("(objectclass=*)");
+
+#define PROP_CONN 0
+#define PROP_AUTHC 1
+#define PROP_AUTHZ 2
+
+typedef struct lookup_info {
+ int last;
+ int flags;
+ const struct propval *list;
+ sasl_server_params_t *sparams;
+} lookup_info;
+
+static slap_response sasl_ap_lookup, sasl_cb_checkpass;
+
+static int
+sasl_ap_lookup( Operation *op, SlapReply *rs )
+{
+ BerVarray bv;
+ AttributeDescription *ad;
+ Attribute *a;
+ const char *text;
+ int rc, i;
+ slap_callback *tmp = op->o_callback;
+ lookup_info *sl = tmp->sc_private;
+
+ if (rs->sr_type != REP_SEARCH) return 0;
+
+ for( i = 0; i < sl->last; i++ ) {
+ const char *name = sl->list[i].name;
+
+ if ( name[0] == '*' ) {
+ if ( sl->flags & SASL_AUXPROP_AUTHZID ) continue;
+ name++;
+ } else if ( !(sl->flags & SASL_AUXPROP_AUTHZID ) )
+ continue;
+
+ if ( sl->list[i].values ) {
+ if ( !(sl->flags & SASL_AUXPROP_OVERRIDE) ) continue;
+ }
+ ad = NULL;
+ rc = slap_str2ad( name, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG( TRANSPORT, DETAIL1,
+ "slap_auxprop: str2ad(%s): %s\n", name, text, 0 );
+#else
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_auxprop: str2ad(%s): %s\n", name, text, 0 );
+#endif
+ continue;
+ }
+ a = attr_find( rs->sr_entry->e_attrs, ad );
+ if ( !a ) continue;
+ if ( ! access_allowed( op, rs->sr_entry, ad, NULL, ACL_AUTH, NULL ) ) {
+ continue;
+ }
+ if ( sl->list[i].values && ( sl->flags & SASL_AUXPROP_OVERRIDE ) ) {
+ sl->sparams->utils->prop_erase( sl->sparams->propctx,
+ sl->list[i].name );
+ }
+ for ( bv = a->a_vals; bv->bv_val; bv++ ) {
+ sl->sparams->utils->prop_set( sl->sparams->propctx,
+ sl->list[i].name, bv->bv_val, bv->bv_len );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+static void
+slap_auxprop_lookup(
+ void *glob_context,
+ sasl_server_params_t *sparams,
+ unsigned flags,
+ const char *user,
+ unsigned ulen)
+{
+ Operation op = {0};
+ int i, doit = 0;
+ Connection *conn = NULL;
+ lookup_info sl;
+
+ sl.list = sparams->utils->prop_get( sparams->propctx );
+ sl.sparams = sparams;
+ sl.flags = flags;
+
+ /* Find our DN and conn first */
+ for( i = 0, sl.last = 0; sl.list[i].name; i++ ) {
+ if ( sl.list[i].name[0] == '*' ) {
+ if ( !strcmp( sl.list[i].name, slap_propnames[PROP_CONN] ) ) {
+ if ( sl.list[i].values && sl.list[i].values[0] )
+ AC_MEMCPY( &conn, sl.list[i].values[0], sizeof( conn ) );
+ if ( !sl.last ) sl.last = i;
+ }
+ if ( (flags & SASL_AUXPROP_AUTHZID) &&
+ !strcmp( sl.list[i].name, slap_propnames[PROP_AUTHZ] ) ) {
+
+ if ( sl.list[i].values && sl.list[i].values[0] )
+ AC_MEMCPY( &op.o_req_ndn, sl.list[i].values[0], sizeof( struct berval ) );
+ if ( !sl.last ) sl.last = i;
+ break;
+ }
+ if ( !strcmp( sl.list[i].name, slap_propnames[PROP_AUTHC] ) ) {
+ if ( !sl.last ) sl.last = i;
+ if ( sl.list[i].values && sl.list[i].values[0] ) {
+ AC_MEMCPY( &op.o_req_ndn, sl.list[i].values[0], sizeof( struct berval ) );
+ if ( !(flags & SASL_AUXPROP_AUTHZID) )
+ break;
+ }
+ }
+ }
+ }
+
+ /* Now see what else needs to be fetched */
+ for( i = 0; i < sl.last; i++ ) {
+ const char *name = sl.list[i].name;
+
+ if ( name[0] == '*' ) {
+ if ( flags & SASL_AUXPROP_AUTHZID ) continue;
+ name++;
+ } else if ( !(flags & SASL_AUXPROP_AUTHZID ) )
+ continue;
+
+ if ( sl.list[i].values ) {
+ if ( !(flags & SASL_AUXPROP_OVERRIDE) ) continue;
+ }
+ doit = 1;
+ }
+
+ if (doit) {
+ slap_callback cb = { NULL, sasl_ap_lookup, NULL, NULL };
+
+ cb.sc_private = &sl;
+
+ op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
+
+ if ( op.o_bd && op.o_bd->be_search ) {
+ SlapReply rs = {REP_RESULT};
+ op.o_tag = LDAP_REQ_SEARCH;
+ op.o_protocol = LDAP_VERSION3;
+ op.o_ndn = conn->c_ndn;
+ op.o_callback = &cb;
+ op.o_time = slap_get_time();
+ op.o_do_not_cache = 1;
+ op.o_is_auth_check = 1;
+ op.o_threadctx = conn->c_sasl_bindop->o_threadctx;
+ op.o_tmpmemctx = conn->c_sasl_bindop->o_tmpmemctx;
+ op.o_tmpmfuncs = conn->c_sasl_bindop->o_tmpmfuncs;
+ op.o_conn = conn;
+ op.o_connid = conn->c_connid;
+ op.o_req_dn = op.o_req_ndn;
+ op.ors_scope = LDAP_SCOPE_BASE;
+ op.ors_deref = LDAP_DEREF_NEVER;
+ op.ors_slimit = 1;
+ op.ors_filter = &generic_filter;
+ op.ors_filterstr = generic_filterstr;
+
+ op.o_bd->be_search( &op, &rs );
+ }
+ }
+}
+
+#if SASL_VERSION_FULL >= 0x020110
+static int
+slap_auxprop_store(
+ void *glob_context,
+ sasl_server_params_t *sparams,
+ struct propctx *prctx,
+ const char *user,
+ unsigned ulen)
+{
+ Operation op = {0};
+ SlapReply rs = {REP_RESULT};
+ int rc, i, j;
+ Connection *conn = NULL;
+ const struct propval *pr;
+ Modifications *modlist = NULL, **modtail = &modlist, *mod;
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ char textbuf[SLAP_TEXT_BUFLEN];
+ const char *text;
+ size_t textlen = sizeof(textbuf);
+
+ /* just checking if we are enabled */
+ if (!prctx) return SASL_OK;
+
+ if (!sparams || !user) return SASL_BADPARAM;
+
+ pr = sparams->utils->prop_get( sparams->propctx );
+
+ /* Find our DN and conn first */
+ for( i = 0; pr[i].name; i++ ) {
+ if ( pr[i].name[0] == '*' ) {
+ if ( !strcmp( pr[i].name, slap_propnames[PROP_CONN] ) ) {
+ if ( pr[i].values && pr[i].values[0] )
+ AC_MEMCPY( &conn, pr[i].values[0], sizeof( conn ) );
+ continue;
+ }
+ if ( !strcmp( pr[i].name, slap_propnames[PROP_AUTHC] ) ) {
+ if ( pr[i].values && pr[i].values[0] ) {
+ AC_MEMCPY( &op.o_req_ndn, pr[i].values[0], sizeof( struct berval ) );
+ }
+ }
+ }
+ }
+ if (!conn || !op.o_req_ndn.bv_val) return SASL_BADPARAM;
+
+ op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
+
+ if ( !op.o_bd || !op.o_bd->be_modify ) return SASL_FAIL;
+
+ pr = sparams->utils->prop_get( prctx );
+ if (!pr) return SASL_BADPARAM;
+
+ for (i=0; pr[i].name; i++);
+ if (!i) return SASL_BADPARAM;
+
+ for (i=0; pr[i].name; i++) {
+ mod = (Modifications *)ch_malloc( sizeof(Modifications) );
+ mod->sml_op = LDAP_MOD_REPLACE;
+ ber_str2bv( pr[i].name, 0, 0, &mod->sml_type );
+ mod->sml_values = (struct berval *)ch_malloc( (pr[i].nvalues + 1) *
+ sizeof(struct berval));
+ for (j=0; j<pr[i].nvalues; j++) {
+ ber_str2bv( pr[i].values[j], 0, 1, &mod->sml_values[j]);
+ }
+ mod->sml_values[j].bv_val = NULL;
+ mod->sml_values[j].bv_len = 0;
+ mod->sml_nvalues = NULL;
+ mod->sml_desc = NULL;
+ *modtail = mod;
+ modtail = &mod->sml_next;
+ }
+ *modtail = NULL;
+
+ rc = slap_mods_check( modlist, 0, &text, textbuf, textlen, NULL );
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = slap_mods_opattrs( &op, modlist, modtail, &text, textbuf,
+ textlen );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ op.o_tag = LDAP_REQ_MODIFY;
+ op.o_protocol = LDAP_VERSION3;
+ op.o_ndn = op.o_req_ndn;
+ op.o_callback = &cb;
+ op.o_time = slap_get_time();
+ op.o_do_not_cache = 1;
+ op.o_is_auth_check = 1;
+ op.o_threadctx = conn->c_sasl_bindop->o_threadctx;
+ op.o_tmpmemctx = conn->c_sasl_bindop->o_tmpmemctx;
+ op.o_tmpmfuncs = conn->c_sasl_bindop->o_tmpmfuncs;
+ op.o_conn = conn;
+ op.o_connid = conn->c_connid;
+ op.o_req_dn = op.o_req_ndn;
+ op.orm_modlist = modlist;
+
+ rc = op.o_bd->be_modify( &op, &rs );
+ }
+ slap_mods_free( modlist );
+ return rc ? SASL_FAIL : SASL_OK;
+}
+#endif /* SASL_VERSION_FULL >= 2.1.16 */
+
+static sasl_auxprop_plug_t slap_auxprop_plugin = {
+ 0, /* Features */
+ 0, /* spare */
+ NULL, /* glob_context */
+ NULL, /* auxprop_free */
+ slap_auxprop_lookup,
+ "slapd", /* name */
+#if SASL_VERSION_FULL >= 0x020110
+ slap_auxprop_store /* the declaration of this member changed
+ * in cyrus SASL from 2.1.15 to 2.1.16 */
+#else
+ NULL
+#endif
+};
+
+static int
+slap_auxprop_init(
+ const sasl_utils_t *utils,
+ int max_version,
+ int *out_version,
+ sasl_auxprop_plug_t **plug,
+ const char *plugname)
+{
+ if ( !out_version | !plug ) return SASL_BADPARAM;
+
+ if ( max_version < SASL_AUXPROP_PLUG_VERSION ) return SASL_BADVERS;
+
+ *out_version = SASL_AUXPROP_PLUG_VERSION;
+ *plug = &slap_auxprop_plugin;
+ return SASL_OK;
+}
+
+typedef struct checkpass_info {
+ int rc;
+ struct berval cred;
+} checkpass_info;
+
+static int
+sasl_cb_checkpass( Operation *op, SlapReply *rs )
+{
+ slap_callback *tmp = op->o_callback;
+ checkpass_info *ci = tmp->sc_private;
+ Attribute *a;
+ struct berval *bv;
+
+ if (rs->sr_type != REP_SEARCH) return 0;
+
+ ci->rc = SASL_NOVERIFY;
+
+ a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_userPassword );
+ if ( !a ) return 0;
+ if ( ! access_allowed( op, rs->sr_entry, slap_schema.si_ad_userPassword,
+ NULL, ACL_AUTH, NULL ) ) return 0;
+
+ for ( bv = a->a_vals; bv->bv_val != NULL; bv++ ) {
+ if ( !lutil_passwd( bv, &ci->cred, NULL, &rs->sr_text ) ) {
+ ci->rc = SASL_OK;
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+slap_sasl_checkpass(
+ sasl_conn_t *sconn,
+ void *context,
+ const char *username,
+ const char *pass,
+ unsigned passlen,
+ struct propctx *propctx)
+{
+ Connection *conn = (Connection *)context;
+ Operation op = {0};
+ int rc;
+ checkpass_info ci;
+
+ ci.rc = SASL_NOUSER;
+
+ /* SASL will fallback to its own mechanisms if we don't
+ * find an answer here.
+ */
+
+ rc = slap_sasl_getdn( conn, NULL, (char *)username, 0, NULL, &op.o_req_ndn,
+ SLAP_GETDN_AUTHCID );
+ if ( rc != LDAP_SUCCESS ) {
+ sasl_seterror( sconn, 0, ldap_err2string( rc ) );
+ return SASL_NOUSER;
+ }
+
+ if ( op.o_req_ndn.bv_len == 0 ) {
+ sasl_seterror( sconn, 0,
+ "No password is associated with the Root DSE" );
+ if ( op.o_req_ndn.bv_val != NULL ) {
+ ch_free( op.o_req_ndn.bv_val );
+ }
+ return SASL_NOUSER;
+ }
+
+ op.o_bd = select_backend( &op.o_req_ndn, 0, 1 );
+ if ( op.o_bd && op.o_bd->be_search ) {
+ slap_callback cb = { NULL, sasl_cb_checkpass, NULL, NULL };
+ SlapReply rs = {REP_RESULT};
+
+ ci.cred.bv_val = (char *)pass;
+ ci.cred.bv_len = passlen;
+
+ cb.sc_private = &ci;
+ op.o_tag = LDAP_REQ_SEARCH;
+ op.o_protocol = LDAP_VERSION3;
+ op.o_ndn = conn->c_ndn;
+ op.o_callback = &cb;
+ op.o_time = slap_get_time();
+ op.o_do_not_cache = 1;
+ op.o_is_auth_check = 1;
+ op.o_threadctx = conn->c_sasl_bindop->o_threadctx;
+ op.o_tmpmemctx = conn->c_sasl_bindop->o_tmpmemctx;
+ op.o_tmpmfuncs = conn->c_sasl_bindop->o_tmpmfuncs;
+ op.o_conn = conn;
+ op.o_connid = conn->c_connid;
+ op.o_req_dn = op.o_req_ndn;
+ op.ors_scope = LDAP_SCOPE_BASE;
+ op.ors_deref = LDAP_DEREF_NEVER;
+ op.ors_slimit = 1;
+ op.ors_filter = &generic_filter;
+ op.ors_filterstr = generic_filterstr;
+
+ op.o_bd->be_search( &op, &rs );
+ }
+ if ( ci.rc != SASL_OK ) {
+ sasl_seterror( sconn, 0,
+ ldap_err2string( LDAP_INVALID_CREDENTIALS ) );
+ }
+
+ ch_free( op.o_req_ndn.bv_val );
+
+ return ci.rc;
+}
+
+/* Convert a SASL authcid or authzid into a DN. Store the DN in an
+ * auxiliary property, so that we can refer to it in sasl_authorize
+ * without interfering with anything else. Also, the SASL username
+ * buffer is constrained to 256 characters, and our DNs could be
+ * much longer (SLAP_LDAPDN_MAXLEN, currently set to 8192)
+ */
+static int
+slap_sasl_canonicalize(
+ sasl_conn_t *sconn,
+ void *context,
+ const char *in,
+ unsigned inlen,
+ unsigned flags,
+ const char *user_realm,
+ char *out,
+ unsigned out_max,
+ unsigned *out_len)
+{
+ Connection *conn = (Connection *)context;
+ struct propctx *props = sasl_auxprop_getctx( sconn );
+ struct propval auxvals[3];
+ struct berval dn;
+ int rc, which;
+ const char *names[2];
+
+ *out_len = 0;
+
+#ifdef NEW_LOGGING
+ LDAP_LOG( TRANSPORT, ENTRY,
+ "slap_sasl_canonicalize: conn %d %s=\"%s\"\n",
+ conn ? conn->c_connid : -1,
+ (flags & SASL_CU_AUTHID) ? "authcid" : "authzid",
+ in ? in : "<empty>");
+#else
+ Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n",
+ conn ? conn->c_connid : -1,
+ (flags & SASL_CU_AUTHID) ? "authcid" : "authzid",
+ in ? in : "<empty>");
+#endif
+
+ /* If name is too big, just truncate. We don't care, we're
+ * using DNs, not the usernames.
+ */
+ if ( inlen > out_max )
+ inlen = out_max-1;
+
+ /* See if we need to add request, can only do it once */
+ prop_getnames( props, slap_propnames, auxvals );
+ if ( !auxvals[0].name )
+ prop_request( props, slap_propnames );
+
+ if ( flags & SASL_CU_AUTHID )
+ which = PROP_AUTHC;
+ else
+ which = PROP_AUTHZ;
+
+ /* Need to store the Connection for auxprop_lookup */
+ if ( !auxvals[PROP_CONN].values ) {
+ names[0] = slap_propnames[PROP_CONN];
+ names[1] = NULL;
+ prop_set( props, names[0], (char *)&conn, sizeof( conn ) );
+ }
+
+ /* Already been here? */
+ if ( auxvals[which].values )
+ goto done;
+
+ /* Normally we require an authzID to have a u: or dn: prefix.
+ * However, SASL frequently gives us an authzID that is just
+ * an exact copy of the authcID, without a prefix. We need to
+ * detect and allow this condition. If SASL calls canonicalize
+ * with SASL_CU_AUTHID|SASL_CU_AUTHZID this is a no-brainer.
+ * But if it's broken into two calls, we need to remember the
+ * authcID so that we can compare the authzID later. We store
+ * the authcID temporarily in conn->c_sasl_dn. We necessarily
+ * finish Canonicalizing before Authorizing, so there is no
+ * conflict with slap_sasl_authorize's use of this temp var.
+ *
+ * The SASL EXTERNAL mech is backwards from all the other mechs,
+ * it does authzID before the authcID. If we see that authzID
+ * has already been done, don't do anything special with authcID.
+ */
+ if ( flags == SASL_CU_AUTHID && !auxvals[PROP_AUTHZ].values ) {
+ conn->c_sasl_dn.bv_val = (char *) in;
+ } else if ( flags == SASL_CU_AUTHZID && conn->c_sasl_dn.bv_val ) {
+ rc = strcmp( in, conn->c_sasl_dn.bv_val );
+ conn->c_sasl_dn.bv_val = NULL;
+ /* They were equal, no work needed */
+ if ( !rc ) goto done;
+ }
+
+ rc = slap_sasl_getdn( conn, NULL, (char *)in, inlen, (char *)user_realm, &dn,
+ (flags & SASL_CU_AUTHID) ? SLAP_GETDN_AUTHCID : SLAP_GETDN_AUTHZID );
+ if ( rc != LDAP_SUCCESS ) {
+ sasl_seterror( sconn, 0, ldap_err2string( rc ) );
+ return SASL_NOAUTHZ;
+ }
+
+ names[0] = slap_propnames[which];
+ names[1] = NULL;
+
+ prop_set( props, names[0], (char *)&dn, sizeof( dn ) );
+
+#ifdef NEW_LOGGING
+ LDAP_LOG( TRANSPORT, ENTRY,
+ "slap_sasl_canonicalize: conn %d %s=\"%s\"\n",
+ conn ? conn->c_connid : -1, names[0]+1,
+ dn.bv_val ? dn.bv_val : "<EMPTY>" );
+#else
+ Debug( LDAP_DEBUG_ARGS, "SASL Canonicalize [conn=%ld]: %s=\"%s\"\n",
+ conn ? conn->c_connid : -1, names[0]+1,
+ dn.bv_val ? dn.bv_val : "<EMPTY>" );
+#endif
+
+done:
+ AC_MEMCPY( out, in, inlen );
+ out[inlen] = '\0';
+
+ *out_len = inlen;
+
+ return SASL_OK;
+}
+
+static int
+slap_sasl_authorize(
+ sasl_conn_t *sconn,
+ void *context,
+ char *requested_user,
+ unsigned rlen,
+ char *auth_identity,
+ unsigned alen,
+ const char *def_realm,
+ unsigned urlen,
+ struct propctx *props)
+{
+ Connection *conn = (Connection *)context;
+ struct propval auxvals[3];
+ struct berval authcDN, authzDN;
+ int rc;
+
+#ifdef NEW_LOGGING
+ LDAP_LOG( TRANSPORT, ENTRY,
+ "slap_sasl_authorize: conn %d authcid=\"%s\" authzid=\"%s\"\n",
+ conn ? conn->c_connid : -1, auth_identity, requested_user);
+#else
+ Debug( LDAP_DEBUG_ARGS, "SASL proxy authorize [conn=%ld]: "
+ "authcid=\"%s\" authzid=\"%s\"\n",
+ conn ? conn->c_connid : -1, auth_identity, requested_user );
+#endif
+ if ( conn->c_sasl_dn.bv_val ) {
+ ch_free( conn->c_sasl_dn.bv_val );
+ conn->c_sasl_dn.bv_val = NULL;
+ conn->c_sasl_dn.bv_len = 0;
+ }
+
+ /* Skip PROP_CONN */
+ prop_getnames( props, slap_propnames+1, auxvals );
+
+ AC_MEMCPY( &authcDN, auxvals[0].values[0], sizeof(authcDN) );
+
+ /* Nothing to do if no authzID was given */
+ if ( !auxvals[1].name || !auxvals[1].values ) {
+ conn->c_sasl_dn = authcDN;
+ goto ok;
+ }
+
+ AC_MEMCPY( &authzDN, auxvals[1].values[0], sizeof(authzDN) );
+
+ rc = slap_sasl_authorized( conn->c_sasl_bindop, &authcDN, &authzDN );
+ ch_free( authcDN.bv_val );
+ if ( rc != LDAP_SUCCESS ) {
+#ifdef NEW_LOGGING
+ LDAP_LOG( TRANSPORT, INFO,
+ "slap_sasl_authorize: conn %ld "
+ "proxy authorization disallowed (%d)\n",
+ (long)(conn ? conn->c_connid : -1), rc, 0 );
+#else
+ Debug( LDAP_DEBUG_TRACE, "SASL Proxy Authorize [conn=%ld]: "
+ "proxy authorization disallowed (%d)\n",
+ (long) (conn ? conn->c_connid : -1), rc, 0 );
+#endif
+
+ sasl_seterror( sconn, 0, "not authorized" );
+ ch_free( authzDN.bv_val );
+ return SASL_NOAUTHZ;
+ }
+
+ conn->c_sasl_dn = authzDN;
+ok:
+ if (conn->c_sasl_bindop) {
+ Statslog( LDAP_DEBUG_STATS,
+ "conn=%lu op=%lu BIND authcid=\"%s\"\n",
+ conn->c_connid, conn->c_sasl_bindop->o_opid,
+ auth_identity, 0, 0);
+ }
+
+#ifdef NEW_LOGGING
+ LDAP_LOG( TRANSPORT, ENTRY,
+ "slap_sasl_authorize: conn %d proxy authorization allowed\n",
+ (long)(conn ? conn->c_connid : -1), 0, 0 );
+#else
+ Debug( LDAP_DEBUG_TRACE, "SASL Authorize [conn=%ld]: "
+ " proxy authorization allowed\n",
+ (long) (conn ? conn->c_connid : -1), 0, 0 );
+#endif
+ return SASL_OK;
+}
+#else