#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 contains a \c uid attribute. * * This function checks, if the entry specified by the DN contains a \c uid * attribute and that this attribute is not empty. * * @param[in] dn DN of the entry. * @return 0 if the \c uid attribute was found, 1 otherwise */ static int dn_contains_uid(const char *dn) { char *attrs[] = { "uid", NULL }; Slapi_Entry *entry = NULL; Slapi_Attr *attr = NULL; Slapi_ValueSet *uid_values = NULL; int rc = 0; rc |= get_entry(dn, attrs, &entry); if (rc != 0 || entry == NULL) { /* dn not found */ rc = 1; goto fail1; } rc |= slapi_entry_attr_find(entry, "uid", &attr); if (rc != 0 || attr == NULL) { /* no uid attribute */ rc = 1; goto fail1; } rc |= slapi_attr_get_valueset(attr, &uid_values); if (rc != 0 || slapi_valueset_count(uid_values) == 0) { /* empty uid value */ rc = 1; goto fail1; } 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). * @return If the DN is a service account, this function returns a zero * terminated string with the name of the service, otherwise it simply * returns a \c NULL pointer. The caller is responsible to free the * returned string with \c slapi_ch_free_string. */ static char* is_service(const char *dn) { char *service = NULL; Slapi_Entry *entry = NULL; char *attrs[] = { "objectClass", "cn", NULL }; Slapi_Attr *attr = NULL; struct berval **cn = NULL; int rc = 0; rc |= get_entry(dn, attrs, &entry); if (rc != 0 || entry == NULL) { /* dn not found */ service = NULL; goto fail1; } if (slapi_entry_attr_hasvalue( entry, "objectClass", "serviceAccount") == 0) { /* no serviceAccount */ service = NULL; goto fail1; } rc |= slapi_entry_attr_find(entry, "cn", &attr); if (rc != 0 || attr == NULL) { /* no cn attribute */ service = NULL; goto fail1; } rc |= slapi_attr_get_values(attr, &cn); if (rc != 0 || cn == NULL) { /* no value in cn attribute */ service = NULL; goto fail2; } if (*cn == NULL) { service = NULL; goto fail2; } service = slapi_ch_strdup((*cn)->bv_val); fail2: ber_bvecfree(cn); fail1: slapi_entry_free(entry); return service; } /** 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; char rdn[255] = {0}; char *old_dn = NULL; char *new_dn = NULL; old_dn = slapi_entry_get_dn(entry); snprintf(rdn, 254, "cn=%s", service); new_dn = slapi_dn_plus_rdn(old_dn, rdn); new_entry = entry_dup_new_dn(entry, new_dn); slapi_ch_free_string(&new_dn); return new_entry; } /** \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 |= dn_contains_uid(parent_dn); 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, slapi_ch_strdup(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; char *auth_method; Slapi_Entry *entry; Slapi_Operation *op; int is_replication; int is_internal; char *service; 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_CONN_AUTHMETHOD, &auth_method); 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 (strcmp(auth_method, SLAPD_AUTH_NONE) == 0) { /* only handle authenticated searches */ return 0; } if (slapi_op_get_type(op) != SLAPI_OPERATION_SEARCH) { return 0; } service = is_service(bind_dn); if (service == NULL) { goto fail1; } result_dn = slapi_entry_get_dn(entry); /* ignore service_password entries */ parent_dn = slapi_dn_parent(result_dn); rc |= dn_contains_uid(parent_dn); slapi_ch_free_string(&parent_dn); if (rc == 0) { rc = -1; goto fail1; } /* modify the dn of the returned entry */ if (dn_contains_uid(result_dn) == 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; } 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_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id); return rc; }