#include #include #include #include /* This is missing in slapi-plugin.h: */ extern int slapi_pw_find(struct berval **vals, struct berval *v); static Slapi_ComponentId *plugin_id = NULL; /** Get an entry specified by a DN and with the specified attributes. * * This functions get a entry specified by a \c DN. It's doing this with an * internal search and limit the requested attributes to the requested * values. The entry is written to an \c Slapi_Entry pointer. * * @param[in] dn \c DN of the requested entry. * @param[in] attrs \c NULL terminated list of requested attributes. * @param[out] entry Pointer to a pointer to \c Slapi_Entry to be filled with * the entry. The caller is responsible to free the filled * pointer with \c slapi_entry_free. * @return * * 0 on success * * not 0 on error (maybe an ldap errno like \c LDAP_NO_SUCH_OBJECT) */ static int get_entry(const char *dn, char **attrs, Slapi_Entry **entry) { Slapi_Entry **entries = NULL; Slapi_PBlock *pb = NULL; int rc = 0; if (entry) { *entry = NULL; } pb = slapi_pblock_new(); slapi_search_internal_set_pb( pb, dn, LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0 /* attrsonly */, NULL /* controls */, NULL /* uniqueid */, plugin_id, 0 /* actions */); slapi_search_internal_pb(pb); slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); if (LDAP_SUCCESS == rc) { slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); if (NULL != entries && NULL != entries[0]) { if (entry) { Slapi_Entry *tmp = NULL; tmp = entries[0]; *entry = slapi_entry_dup(tmp); } } else { /* No entry there */ rc = LDAP_NO_SUCH_OBJECT; } } slapi_free_search_results_internal(pb); slapi_pblock_destroy(pb); return rc; } /** Check if the given DN is a user account. * * This functions checks, if the given DN is a DN of a user account. All user * accounts have to have the \c splineAccount object class. * * @param[in] dn DN of the entry. * @param[out] gold_account If not \c NULL, this method will also check, if * the entry has the \c splineGoldAccount object * class. If the entry is a gold account this will * be set to 1, otherwise 0. * * @return 0 if the entry is a user account, 1 otherwise */ static int is_user(const char *dn, int *gold_account) { char *attrs[] = { "objectClass", NULL }; Slapi_Entry *entry = NULL; int rc = 0; if (gold_account != NULL) { *gold_account = 0; } rc |= get_entry(dn, attrs, &entry); if (rc != 0 || entry == NULL) { /* dn not found */ rc = 1; goto fail1; } if (slapi_entry_attr_hasvalue( entry, "objectClass", "splineAccount") == 0) { /* no user account */ rc = 1; goto fail1; } /* check if this user has a "gold" account */ if (gold_account != NULL) { if (slapi_entry_attr_hasvalue( entry, "objectClass", "splineGoldAccount") != 0) { *gold_account = 1; } } fail1: slapi_entry_free(entry); return rc; } /** Check if the given DN is a service account. * * This function checks, if the given DN is a DN of a service account. All * services have to do a bind on such a DN before a search so that this plugin * can identify the service and rewrite the search results to the service * specific password entries. * * @param[in] dn The DN to check (most times this should be the bound DN of the * connection). * @param[out] service Pointer to a char pointer that will be filled with the * service name. The caller is responsible to free the * filled pointer with \c slapi_ch_free_string. * If this is NULL, the function will only check if the DN * is a service account and will not allocate memory for the * service name. * @param[out] gold_service If not \c NULL, this method will also check, if * the service requires a \c splineGoldAccount. * @return 0 if the entry is a service account, 1 otherwise. */ static int is_service(const char *dn, char **service, int *gold_service) { Slapi_Entry *entry = NULL; char *attrs[] = { "objectClass", "cn", NULL }; Slapi_Attr *attr = NULL; struct berval **cn = NULL; int rc = 0; if (gold_service != NULL) { *gold_service = 0; } rc |= get_entry(dn, attrs, &entry); if (rc != 0 || entry == NULL) { /* dn not found */ rc = 1; goto fail1; } if (slapi_entry_attr_hasvalue( entry, "objectClass", "serviceAccount") == 0) { /* no serviceAccount */ rc = 1; goto fail1; } if (gold_service != NULL) { if (slapi_entry_attr_hasvalue( entry, "objectClass", "goldServiceAccount") != 0) { *gold_service = 1; } } rc |= slapi_entry_attr_find(entry, "cn", &attr); if (rc != 0 || attr == NULL) { /* no cn attribute */ rc = 1; goto fail1; } rc |= slapi_attr_get_values(attr, &cn); if (rc != 0 || cn == NULL) { /* no value in cn attribute */ rc = 1; goto fail2; } if (*cn == NULL) { rc = 1; goto fail2; } if (service) { *service = slapi_ch_strdup((*cn)->bv_val); } fail2: ber_bvecfree(cn); fail1: slapi_entry_free(entry); return rc; } /** Try to authenticate agains a specific DN with given credentials. * * This functions simply checks if the given credentials match the saved * password in the \c userPassword attribute of the entry specified by the * given DN. This function does nothing magic and use a slapi method to * check the password and should support therefore all the password hashing * schemes supported by the slapd. * * @param[in] dn The DN of the entry. * @param[in] credentials A \c berval with the supplied password. * * @return * * 0 is auth was successfull * * -1 if the specified DN was not found * * 1 in all other cases */ static int auth(char *dn, struct berval *credentials) { char *attrs[] = { "userPassword", NULL }; Slapi_Entry *entry = NULL; Slapi_Attr *attr = NULL; struct berval **userPasswords = NULL; int rc = 0; /* get entry */ rc |= get_entry(dn, attrs, &entry); if (rc != 0 || entry == NULL) { rc = -1; goto fail1; } /* get userpassword attr */ rc |= slapi_entry_attr_find(entry, "userPassword", &attr); if (rc != 0 || attr == NULL) { rc = 1; goto fail1; } /* get attribute values */ rc |= slapi_attr_get_values(attr, &userPasswords); if (rc != 0 || userPasswords == NULL) { rc = 1; goto fail2; } /* check password */ rc |= slapi_pw_find(userPasswords, credentials); fail2: ber_bvecfree(userPasswords); fail1: slapi_entry_free(entry); return rc; } /** Check if the supplied credentials match the stored password of a given entry * or the parent entry (if the given entry does not exists). * * This function tries to check the supplied credentials against the * \c userPassword attribute of the entry specified by the supplied DN. * If the supplied DN does not exists, it will try to authenticate agains * the direct parent of the specified DN. If the entry exists, but does not * have a \c userPassword attribute or the attribute is empty, the * authentication will fail. * * @param[in] dn The DN of the entry. * @param[in] credentials A \c berval with the supplied password. * * @return * * 0 if the authentication was successfull * * 1 otherwise */ static int auth_with_password_fallback(char *dn, struct berval *credentials) { char *parent_dn = NULL; int rc = 0; char fn[] = "auth_with_password_fallback in service_passwords plug-in"; slapi_log_error( SLAPI_LOG_PLUGIN, fn, "Try password fallback with dn: %s.\n", dn); rc = auth(dn, credentials); if (rc == 0) { /* auth success */ return 0; } else if (rc == 1) { /* auth fail, but entry exists (no fallback) */ return 1; } /* fallback to parent dn */ parent_dn = slapi_dn_parent(dn); rc = auth(parent_dn, credentials); slapi_ch_free_string(&parent_dn); if (rc == 0) { /* auth success */ return 0; } return 1; } /** Add an attribute to an entry. * * This function adds the given attribute to the given entry. The attribute is * copied and merged with all values that are already available for the type * of the attribute. * * @param[in,out] entry Entry to be modified. * @param[in] attr Attribute that should be added to the entry. The attribute * values are copied to the entry. */ static void entry_add_attr(Slapi_Entry *entry, Slapi_Attr *attr) { struct berval **vals = NULL; char *type = NULL; slapi_attr_get_type(attr, &type); slapi_attr_get_values(attr, &vals); slapi_entry_attr_merge(entry, type, vals); ber_bvecfree(vals); } /** Copy all attributes from one \c Slapi_Entry to another. * * This function iterates over all attributes in \c from and adds the attributes * to \c to. Attributes already contained in \c to will be preserved. * * @param[in,out] to Target for the copy operation. * @param[in] from Source of the attribuutes to copy. */ static void entry_copy_attrs(Slapi_Entry *to, Slapi_Entry *from) { Slapi_Attr *attr = NULL; slapi_entry_first_attr(from, &attr); while (attr) { entry_add_attr(to, attr); slapi_entry_next_attr(from, attr, &attr); } } /** Create a copy of an \c Slapi_Entry with a new DN. * * This function creates a copy of a \c Slapi_Entry without using * \c slapi_entry_dup because, that would copy the two internal values for the * DN, that cannot be free'd with slapi_* methods. So this creates a new * \c Slapi_Entry, set the new DN and copies all the attributes from the old * entry to the newly allocated entry. The caller has to ensure, that the returned \c Slapi_Entry get free'd. * * @param[in] entry Source entry to create a copy from. * @param[in] dn The new DN of the copied entry. * @return The newly allocated \c Slapi_Entry. */ static Slapi_Entry *entry_dup_new_dn(Slapi_Entry *entry, char *dn) { Slapi_Entry *new_entry = NULL; new_entry = slapi_entry_alloc(); slapi_entry_set_dn(new_entry, dn); entry_copy_attrs(new_entry, entry); return new_entry; } /** Creates a copy of the entry and prepend the service prefix to the DN. * * This function creates a copy of the supplied entry and prepend its DN with * the "cn=" prefix. The caller is responsible for freeing the memory * of the returned new \c Slapi_Entry. * * @param[in] entry The source entry for the copy with the original DN * value. All attributes will be in the newly created entry, * too. * @param[in] service The name of the service, that should be contained in the * prefix for the new DN. The service name should be not * longer than 251 chars (longer values will be truncated). * @return A new \c Slapi_Entry with the modified DN. */ static Slapi_Entry *prepend_service_prefix(Slapi_Entry *entry, char *service) { Slapi_Entry *new_entry = NULL; Slapi_RDN *rdn = NULL; char *old_dn = NULL; char *new_dn = NULL; old_dn = slapi_entry_get_dn(entry); rdn = slapi_rdn_new(); slapi_rdn_add(rdn, "cn", service); new_dn = slapi_dn_plus_rdn(old_dn, slapi_rdn_get_rdn(rdn)); slapi_rdn_free(&rdn); new_entry = entry_dup_new_dn(entry, new_dn); slapi_ch_free_string(&new_dn); return new_entry; } /** The the name of the service specified by the virtual entry for a user. * * This function splits the DN in single RDN values, get the first and returns * the value if the type is "cn" (aka. commonName). * * @param[in] dn The dn of the virtual entry. * @return Pointer to the service name or NULL if the type of the first RDN is * not "cn". If a pointer is returned the caller is responsible for * freeing it with \c slapi_ch_free_string. */ static char *get_virtual_service(const char *dn) { Slapi_RDN *rdn = NULL; char *service = NULL; char *type = NULL; char *value = NULL; rdn = slapi_rdn_new(); slapi_rdn_set_dn(rdn, dn); slapi_rdn_get_first(rdn, &type, &value); if (strcmp(type, "cn") == 0) { service = slapi_ch_strdup(value); } slapi_rdn_free(&rdn); return service; } /** The DN of the service specified by name. * * This function builds the \c DN of a service from the service name and * the base dn for the services. * * @param[in] service The name of the service. * @return Pointer to the service dn. The caller is responsible for freeing * it with \c slapi_ch_free_string. */ static char *get_service_dn(const char *service) { char *new_dn = NULL; char *base_dn = NULL; Slapi_RDN *rdn = NULL; /* TODO: Get base_dn */ rdn = slapi_rdn_new(); slapi_rdn_add(rdn, "cn", service); new_dn = slapi_dn_plus_rdn(base_dn, slapi_rdn_get_rdn(rdn)); slapi_rdn_free(&rdn); return new_dn; } /** \c PRE_BIND plugin to allow password fallback. * * This function is called before a bind operation. If the BIND_DN is a user * entry with a \c cn= prefix we try to authenticate against the * service password entry or fallback to the user entry if the serice password * does not exist. * * @param[in,out] pb Parameter block of the operation. * @return * * SLAPI_BIND_SUCCESS: \b CONFUSING! This does not force the bind to * succeed. This simply means to continue the processing and handle the bind * in the backend. * * SLAPI_BIND_FAIL: This force the bind to fail and send * LDAP_INVALID_CREDENTIALS back to the client. * * All other values causes special handling. If no error is set and * LDAP_CONN_DN in the pb is set to a value the bind succeed. Otherwise the * bind will fail. But in both cases this method is responsible for sending * the response to the client. */ static int pre_bind(Slapi_PBlock *pb) { char *dn = NULL; int method = 0; struct berval *credentials = NULL; int is_replication = 0; int is_internal = 0; char *parent_dn = NULL; int rc = 0; char fn[] = "pre_bind in service_passwords plug-in"; /* Obtain the bind information from the parameter block. */ rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn); rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); rc |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &credentials); rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replication); rc |= slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION, &is_internal); if (rc != 0) { slapi_log_error( SLAPI_LOG_PLUGIN, fn, "Could not get parameters for bind operation (error %d).\n", rc); /* Cancel bind */ slapi_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL); return LDAP_OPERATIONS_ERROR; } if (is_replication || is_internal) { return SLAPI_BIND_SUCCESS; } if (method != LDAP_AUTH_SIMPLE) { slapi_log_error( SLAPI_LOG_PLUGIN, fn, "The service_passwords plug-in does not handle this bind " "(method %d).\n", method); return SLAPI_BIND_SUCCESS; } parent_dn = slapi_dn_parent(dn); rc |= is_user(parent_dn, NULL); slapi_ch_free_string(&parent_dn); if (rc != 0) { /* parent_dn is not an user, so we ignore this bind request. */ return SLAPI_BIND_SUCCESS; } if (auth_with_password_fallback(dn, credentials) == 0) { /* auth success: set connection info */ rc |= slapi_pblock_set(pb, SLAPI_CONN_DN, dn); if (rc != 0) { slapi_log_error( SLAPI_LOG_PLUGIN, fn, "Failed to set connection info.\n"); rc = LDAP_OPERATIONS_ERROR; slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL); return rc; } /* auth done */ slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); return 1; } /* auth fail */ return SLAPI_BIND_FAIL; } /** \c PRE_ENTRY plugin to rewrite returned search results to keep the service * name until bind. * * We want to have optional service specific password. Therefore we need to know * the service during bind of the user. To keep this information, we add a * simple (virtual) service prefix to the user DN. If a service search for * users, it has to do a bind with a service account before. The service name is * extracted from this bind and the dn of the found entries is extended to * \c cn=,. During a bind the plugin can extract the service * name and do magic stuff. * * This function is called for each search result entry before sending it back * to the client and the result can be change by updating the parameter block. * * @param[in,out] pb Parameter block of the operation. * @return * * >= 0 send the result to the client and continue * * < 0 do not send this result, but continue with the other results */ static int pre_entry(Slapi_PBlock *pb) { char *bind_dn; Slapi_Entry *entry; Slapi_Operation *op; int is_replication; int is_internal; char *service = NULL; char *parent_dn = NULL; const char *result_dn = NULL; Slapi_Entry *new_entry; int rc = 0; char fn[] = "pre_entry in service_passwords plug-in"; rc |= slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn); rc |= slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_ENTRY, &entry); rc |= slapi_pblock_get(pb, SLAPI_OPERATION, &op); rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replication); rc |= slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION, &is_internal); if (rc != 0) { slapi_log_error( SLAPI_LOG_PLUGIN, fn, "Could not get parameters (error %d).\n", rc); slapi_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL); return LDAP_OPERATIONS_ERROR; } if (is_replication || is_internal) { return 0; } if (bind_dn == NULL) { /* only handle authenticated searches */ return 0; } if (slapi_op_get_type(op) != SLAPI_OPERATION_SEARCH) { return 0; } if (is_service(bind_dn, &service, NULL) != 0) { parent_dn = slapi_dn_parent(bind_dn); rc |= is_user(parent_dn, NULL); slapi_ch_free_string(&parent_dn); if (rc != 0) { return 0; } service = get_virtual_service(bind_dn); } result_dn = slapi_entry_get_dn(entry); /* ignore service_password entries */ parent_dn = slapi_dn_parent(result_dn); rc |= is_user(parent_dn, NULL); slapi_ch_free_string(&parent_dn); if (rc == 0) { rc = -1; goto fail1; } /* modify the dn of the returned entry */ if (is_user(result_dn, NULL) == 0) { new_entry = prepend_service_prefix(entry, service); /* Set the new entry as the new result in the pblock and also set the the REP_ENTRY_MUSTBEFREED flag, so that the entry gets free'd when ready. */ slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_ENTRY, new_entry); } fail1: slapi_ch_free_string(&service); return rc; } /** \c PRE_SEARCH plugin to rewrite the search base if it is set to the virtual * service entry. * * Most applications want to get the user information of a user. Some might get * this information by searching with the user DN as base DN. This does not work * for the virtual service DNs, because this entries might not exists. * * So for all searches that are executed in an authenticated connection * (authenticated as service account or user) the virtual prefix is dropped, if * the base DN is the virtual user entry. The \c PRE_ENTRY hook rewrites the * found user entry to the requested virtual user entry. This does nocht work, * if the client explicitly searches with a search filter for \c cn, but it is * sufficient. * * @param[in,out] pb Parameter block of the operation. * @return The return value do not have influence on the search operation by * itself. It only stops further plugin processing (other plguins), if * it is not equal 0. */ static int pre_search(Slapi_PBlock *pb) { char *bind_dn; char *base; int is_replication; int is_internal; char *parent_dn = NULL; int rc = 0; char fn[] = "pre_search in service_passwords plug-in"; rc |= slapi_pblock_get(pb, SLAPI_CONN_DN, &bind_dn); rc |= slapi_pblock_get(pb, SLAPI_TARGET_DN, &base); rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_replication); rc |= slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION, &is_internal); if (rc != 0) { slapi_log_error( SLAPI_LOG_PLUGIN, fn, "Could not get parameters (error %d).\n", rc); slapi_send_ldap_result( pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL); return LDAP_OPERATIONS_ERROR; } if (is_replication || is_internal) { return 0; } if (bind_dn == NULL) { /* only handle authenticated searches */ return 0; } if (is_service(bind_dn, NULL, NULL) != 0) { parent_dn = slapi_dn_parent(bind_dn); rc |= is_user(parent_dn, NULL); slapi_ch_free_string(&parent_dn); if (rc != 0) { return 0; } } parent_dn = slapi_dn_parent(base); if (is_user(parent_dn, NULL) == 0) { rc |= slapi_pblock_set(pb, SLAPI_TARGET_DN, parent_dn); } slapi_ch_free_string(&parent_dn); return rc; } Slapi_PluginDesc bindpdesc = { .spd_id = "service_passwords", .spd_vendor = "spline", .spd_version = "1.0", .spd_description = "preoperation plugin " "to authenticate a bind against different passwords" }; int service_passwords_init(Slapi_PBlock *pb) { int rc = 0; rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_CURRENT_VERSION); rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *) &bindpdesc); rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, (void *) pre_bind); rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ENTRY_FN, (void *) pre_entry); rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *) pre_search); rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id); return rc; }