On Wed, Sep 24, 2014 at 03:23:54PM +0200, Jakub Hrozek wrote: > On Tue, Sep 23, 2014 at 05:11:01PM +0200, Sumit Bose wrote: > > Hi, > > > > this patch should fix https://fedorahosted.org/freeipa/ticket/4031 and > > with the corresponding SSSD part it would be possible to get the full > > list of group memberships with the id command even for user who didn't > > log in before. > > > > bye, > > Sumit > > So far I only read the patch, no testing was done yet (I'm installing a > separate VM where I'll keep this new plugin for easy comparison and > backwards-compatibility testing)
Thank you for the review, please see comments below. > > First, there are some Coverity warnings: > > Error: USE_AFTER_FREE (CWE-825): > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:242: > alias: Assigning: "groups" = "new_groups". Now both point to the same > storage. > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:246: > freed_arg: "free(void *)" frees "groups". > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:252: > use_after_free: Using freed pointer "groups". fixed > > Error: CONSTANT_EXPRESSION_RESULT (CWE-398): > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:596: > missing_parentheses: "!id_type != SSS_ID_TYPE_GID" is always true regardless > of the values of its operands. Did you intend to either negate the entire > comparison expression, in which case parentheses would be required around the > entire comparison expression to force that interpretation, or negate the > sense of the comparison (that is, use '==' rather than '!=')? This occurs as > the logical second operand of '||'. fixed > > Error: DEADCODE (CWE-561): > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:594: > cond_cannot_single: Condition "request_type == 1U", taking false branch. Now > the value of "request_type" cannot be equal to 1. > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:594: > cond_cannot_set: Condition "request_type == 3U", taking false branch. Now > the value of "request_type" cannot be equal to any of {1, 3}. > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:606: > cannot_set: At condition "request_type == 1U", the value of "request_type" > cannot be equal to any of {1, 3}. > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:606: > dead_error_condition: The condition "request_type == 1U" cannot be true. > freeipa-4.0.0GIT2563ea2/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c:607: > dead_error_line: Execution cannot reach this statement "ret = > pack_ber_sid(sid_str,...". I thik this is a result of the CONSTANT_EXPRESSION_RESULT issue, since I fixed it this warning should be gone as well. > > See some comments inline. > > > From 23ff38cdea85995b211e73f474bcb4b0d7fb8039 Mon Sep 17 00:00:00 2001 > > From: Sumit Bose <sb...@redhat.com> > > Date: Tue, 23 Sep 2014 15:55:43 +0200 > > Subject: [PATCH] extdom: add support for new version > > > > Currently the extdom plugin is basically used to translate SIDs of AD > > users and groups to names and POSIX IDs. > > > > With this patch a new version is added which will return the full member > > list for groups and the full list of group memberships for a user. > > Additionally the gecos field, the home directory and the login shell of a > > user are returned and an optional list of key-value pairs which > > currently will contain the SID of the requested object if available. > > > > https://fedorahosted.org/freeipa/ticket/4031 > > --- > > .../ipa-extdom-extop/ipa_extdom.h | 29 +- > > .../ipa-extdom-extop/ipa_extdom_common.c | 850 > > +++++++++++++++------ > > .../ipa-extdom-extop/ipa_extdom_extop.c | 28 +- > > 3 files changed, 640 insertions(+), 267 deletions(-) > > > > diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h > > b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h > > index > > 5f834a047a579104cd2589ce417c580c1c5388d3..548ee74f561c474854c049726c4c3e71da5cbbea > > 100644 > > --- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h > > +++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h > > @@ -64,6 +64,7 @@ > > #include <sss_nss_idmap.h> > > > > #define EXOP_EXTDOM_OID "2.16.840.1.113730.3.8.10.4" > > +#define EXOP_EXTDOM_V2_OID "2.16.840.1.113730.3.8.10.4.1" > > It's a bit odd that this control is called V1 in the SSSD tree and V2 in > the IPA tree. It's not wrong, just strange maybe. you are right, I renamed the versions here. > > > > > -int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req, > > - struct extdom_res **res) > > +int check_request(struct extdom_req *req, enum extdom_version version) > > +{ > > + if (version == EXTDOM_V1) { > > + if (req->request_type == REQ_FULL_WITH_GROUPS) { > > + return LDAP_PROTOCOL_ERROR; > > + } > > + } > > Any particular reason why these conditions are nested and not and-ed ? > Did you expect more under the EXTDOM_V1 condition? I'm not expecting them, but who knows :-) I think this way it is more clear that we are testing features of a specific version here. > > > + > > + return LDAP_SUCCESS; > > +} > > + > > +static int get_buffer(size_t *_buf_len, char **_buf) > > { > > - int ret; > > - char *domain_name = NULL; > > - char *sid_str = NULL; > > - size_t buf_len; > > - char *buf = NULL; > > long pw_max; > > long gr_max; > > - struct pwd_grp pg_data; > > - struct passwd *pwd_result = NULL; > > - struct group *grp_result = NULL; > > - enum sss_id_type id_type; > > - char *fq_name = NULL; > > - char *sep; > > - > > + size_t buf_len; > > + char *buf; > > > > pw_max = sysconf(_SC_GETPW_R_SIZE_MAX); > > gr_max = sysconf(_SC_GETGR_R_SIZE_MAX); > > @@ -211,302 +212,655 @@ int handle_request(struct ipa_extdom_ctx *ctx, > > struct extdom_req *req, > > return LDAP_OPERATIONS_ERROR; > > } > > > > - switch (req->input_type) { > > - case INP_POSIX_UID: > > - if (req->request_type == REQ_SIMPLE) { > > - ret = sss_nss_getsidbyid(req->data.posix_uid.uid, &sid_str, > > - &id_type); > > + *_buf_len = buf_len; > > + *_buf = buf; > > + > > + return LDAP_SUCCESS; > > +} > > + > > +static int get_user_grouplist(const char *name, gid_t gid, > > + size_t *_ngroups, gid_t **_groups ) > > +{ > > + int ret; > > + int ngroups; > > + gid_t *groups; > > + gid_t *new_groups; > > + > > + ngroups = 128; > > I was wondering whether to use _SC_NGROUPS_MAX or NGROUPS_MAX here, but > I guess you're right it's very unlikely that a user will be a member of > more than 128 groups so we'd just clinge to more memory than needed.. > > > + groups = malloc(ngroups * sizeof(gid_t)); > > + if (groups == NULL) { > > + return LDAP_OPERATIONS_ERROR; > > + } > > + > > + ret = getgrouplist(name, gid, groups, &ngroups); > > + if (ret == -1) { > > + new_groups = realloc(groups, ngroups); > > + if (new_groups == NULL) { > > + free(groups); > > + return LDAP_OPERATIONS_ERROR; > > + } > > + groups = new_groups; > > + > > + ret = getgrouplist(name, gid, groups, &ngroups); > > + if (ret == -1) { > > + free(groups); > > + ret = LDAP_OPERATIONS_ERROR; > > + } > > + } > > + > > + *_ngroups = ngroups; > > + *_groups = groups; > > + > > + return LDAP_SUCCESS; > > +} > > + > > +static int pack_ber_sid(const char *sid, struct berval **berval) > > +{ > > + BerElement *ber = NULL; > > + int ret; > > + > > + ber = ber_alloc_t( LBER_USE_DER ); > > + if (ber == NULL) { > > + return LDAP_OPERATIONS_ERROR; > > + } > > + > > + ret = ber_printf(ber,"{es}", RESP_SID, sid); > > + if (ret == -1) { > > + ber_free(ber, 1); > > + return LDAP_OPERATIONS_ERROR; > > + } > > + > > + ret = ber_flatten(ber, berval); > > + ber_free(ber, 1); > > + if (ret == -1) { > > + return LDAP_OPERATIONS_ERROR; > > + } > > + > > + return LDAP_SUCCESS; > > +} > > + > > +#define SSSD_SYSDB_SID_STR "objectSIDString" > > + > > +static int pack_ber_user(const char *domain_name, const char *user_name, > > + uid_t uid, gid_t gid, > > + const char *gecos, const char *homedir, > > + const char *shell, const char *sid_str, > > + struct berval **berval) > > +{ > > + BerElement *ber = NULL; > > + int ret; > > + enum response_types response_type; > > + size_t ngroups; > > + gid_t *groups = NULL; > > + size_t buf_len; > > + char *buf = NULL; > > + struct group grp; > > + struct group *grp_result; > > + size_t c; > > + char *locat; > > + char *short_user_name = NULL; > > + const char *single_value_string_array[] = {NULL, NULL}; > > + > > + if (gecos == NULL && homedir == NULL && shell == NULL) { > > + response_type = RESP_USER; > > + } else { > > + response_type = RESP_USER_GROUPLIST; > > + } > > + > > + short_user_name = strdup(user_name); > > + if ((locat = strchr(short_user_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { > > Some functions in the code use strchr to fund the at-sign, some use > strrch. Could we standardize on one or the other? Do you expect some > usernames with an at-sign in them? I think the 'rr' version was just a typo, I changed it to 'r'. > > > + if (strcasecmp(locat+1, domain_name) == 0 ) { > > + locat[0] = '\0'; > > } else { > > - id_type = SSS_ID_TYPE_UID; > > - ret = getpwuid_r(req->data.posix_uid.uid, &pg_data.data.pwd, > > buf, > > - buf_len, &pwd_result); > > + ret = LDAP_NO_SUCH_OBJECT; > > + goto done; > > } > > + } > > > > - domain_name = strdup(req->data.posix_uid.domain_name); > > - break; > > - case INP_POSIX_GID: > > - if (req->request_type == REQ_SIMPLE) { > > - ret = sss_nss_getsidbyid(req->data.posix_uid.uid, &sid_str, > > - &id_type); > > + ber = ber_alloc_t( LBER_USE_DER ); > > + if (ber == NULL) { > > + return LDAP_OPERATIONS_ERROR; > > + } > > + > > + ret = ber_printf(ber,"{e{ssii", response_type, domain_name, > > short_user_name, > > + uid, gid); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + > > + if (response_type == RESP_USER_GROUPLIST) { > > + ret = get_user_grouplist(user_name, gid, &ngroups, &groups); > > + if (ret != LDAP_SUCCESS) { > > + goto done; > > + } > > + > > + ret = get_buffer(&buf_len, &buf); > > + if (ret != LDAP_SUCCESS) { > > + goto done; > > + } > > + > > + ret = ber_printf(ber,"sss", gecos, homedir, shell); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + > > + ret = ber_printf(ber,"{"); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + > > + for (c = 0; c < ngroups; c++) { > > + ret = getgrgid_r(groups[c], &grp, buf, buf_len, &grp_result); > > + if (ret != 0) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + if (grp_result == NULL) { > > + ret = LDAP_NO_SUCH_OBJECT; > > + goto done; > > I wanted to check if you think it's better to continue or fail here. Did > you opt for failing because you were afraid of missing some deny access > checks in case we couldn't resolv a group? I think there is a disconnect if getgrouplist() returns a GID that cannot be resolved so I prefer an error in this case. > > > + } > > + > > + ret = ber_printf(ber, "s", grp.gr_name); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + } > > + > > + ret = ber_printf(ber,"}"); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + > > + single_value_string_array[0] = sid_str; > > + ret = ber_printf(ber,"{{s{v}}}", SSSD_SYSDB_SID_STR, > > + single_value_string_array); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + } > > + > > + ret = ber_printf(ber,"}}"); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + > > + ret = ber_flatten(ber, berval); > > + if (ret == -1) { > > + ret = LDAP_OPERATIONS_ERROR; > > + goto done; > > + } > > + > > + ret = LDAP_SUCCESS; > > +done: > > + free(short_user_name); > > + free(groups); > > + free(buf); > > + ber_free(ber, 1); > > + return ret; > > +} > > + > > +static int pack_ber_group(const char *domain_name, const char *group_name, > > + gid_t gid, char **members, const char *sid_str, > > + struct berval **berval) > > +{ > > + BerElement *ber = NULL; > > + int ret; > > + size_t c; > > + char *locat; > > + char *short_group_name = NULL; > > + const char *single_value_string_array[] = {NULL, NULL}; > > + > > + short_group_name = strdup(group_name); > > + if ((locat = strchr(short_group_name, SSSD_DOMAIN_SEPARATOR)) != NULL) > > { > > + if (strcasecmp(locat+1, domain_name) == 0 ) { > > + locat[0] = '\0'; > > } else { > > - id_type = SSS_ID_TYPE_GID; > > - ret = getgrgid_r(req->data.posix_gid.gid, &pg_data.data.grp, > > buf, > > - buf_len, &grp_result); > > + ret = LDAP_NO_SUCH_OBJECT; > > + goto done; > > } > > + } > > > > - domain_name = strdup(req->data.posix_gid.domain_name); > > - break; > > - case INP_SID: > > - ret = sss_nss_getnamebysid(req->data.sid, &fq_name, &id_type); > > - if (ret != 0) { > > + ber = ber_alloc_t( LBER_USE_DER ); > > + if (ber == NULL) { > > + return LDAP_OPERATIONS_ERROR; > > + } > > + > > + ret = ber_printf(ber,"{e{ssi", members == NULL ? RESP_GROUP > > + : RESP_GROUP_MEMBERS, > > Each pack_ber_group is called like this: > 718 if (request_type == REQ_FULL) { > 719 ret = pack_ber_group(domain_name, grp.gr_name, grp.gr_gid, > 720 NULL, NULL, berval); > 721 } else { > 722 ret = pack_ber_group(domain_name, grp.gr_name, grp.gr_gid, > 723 grp.gr_mem, sid, berval); > 724 } > > And then you guess the request_type again based on the parameter > values. Isn't it safer to add the request type parameter avoid the if-else > switch in the callers? Or were you trying to be on the safe side to avoid > checking the validity members array in the pack_ber_group function and have > the array set to NULL by the caller? You are right, the if-block is odd. Instead of the request_type I added the response_type to the argument list of pack_ber_user() and pack_ber_group() which I think is more natural because it is the response that is packed. > > The rest of the file looks to me, just the same "issue" with guessing the > request type is repeated. > New version attached. bye, Sumit
From f7de5e8330c3bdd6d9d21335df6c04e54ce19ed8 Mon Sep 17 00:00:00 2001 From: Sumit Bose <sb...@redhat.com> Date: Tue, 23 Sep 2014 15:55:43 +0200 Subject: [PATCH] extdom: add support for new version Currently the extdom plugin is basically used to translate SIDs of AD users and groups to names and POSIX IDs. With this patch a new version is added which will return the full member list for groups and the full list of group memberships for a user. Additionally the gecos field, the home directory and the login shell of a user are returned and an optional list of key-value pairs which currently will contain the SID of the requested object if available. https://fedorahosted.org/freeipa/ticket/4031 --- .../ipa-extdom-extop/ipa_extdom.h | 29 +- .../ipa-extdom-extop/ipa_extdom_common.c | 828 ++++++++++++++------- .../ipa-extdom-extop/ipa_extdom_extop.c | 28 +- 3 files changed, 617 insertions(+), 268 deletions(-) diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h index 5f834a047a579104cd2589ce417c580c1c5388d3..90f8390d871a698dc00ef56c41be0749eaa13424 100644 --- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h +++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom.h @@ -64,6 +64,7 @@ #include <sss_nss_idmap.h> #define EXOP_EXTDOM_OID "2.16.840.1.113730.3.8.10.4" +#define EXOP_EXTDOM_V1_OID "2.16.840.1.113730.3.8.10.4.1" #define IPA_EXTDOM_PLUGIN_NAME "ipa-extdom-extop" #define IPA_EXTDOM_FEATURE_DESC "IPA trusted domain ID mapper" @@ -71,6 +72,11 @@ #define IPA_PLUGIN_NAME IPA_EXTDOM_PLUGIN_NAME +enum extdom_version { + EXTDOM_V0 = 0, + EXTDOM_V1 +}; + enum input_types { INP_SID = 1, INP_NAME, @@ -80,14 +86,17 @@ enum input_types { enum request_types { REQ_SIMPLE = 1, - REQ_FULL + REQ_FULL, + REQ_FULL_WITH_GROUPS }; enum response_types { RESP_SID = 1, RESP_NAME, RESP_USER, - RESP_GROUP + RESP_GROUP, + RESP_USER_GROUPLIST, + RESP_GROUP_MEMBERS }; struct extdom_req { @@ -123,11 +132,18 @@ struct extdom_res { char *user_name; uid_t uid; gid_t gid; + char *gecos; + char *home; + char *shell; + size_t ngroups; + char **groups; } user; struct { char *domain_name; char *group_name; gid_t gid; + size_t nmembers; + char **members; } group; } data; }; @@ -150,15 +166,14 @@ struct pwd_grp { struct passwd pwd; struct group grp; } data; + int ngroups; + gid_t *groups; }; int parse_request_data(struct berval *req_val, struct extdom_req **_req); void free_req_data(struct extdom_req *req); +int check_request(struct extdom_req *req, enum extdom_version version); int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req, - struct extdom_res **res); -int create_response(struct extdom_req *req, struct pwd_grp *pg_data, - const char *sid_str, enum sss_id_type id_type, - const char *domain_name, struct extdom_res **_res); -void free_resp_data(struct extdom_res *res); + struct berval **berval); int pack_response(struct extdom_res *res, struct berval **ret_val); #endif /* _IPA_EXTDOM_H_ */ diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c index 025d37dc5eda05c8db43d4e8176fd7898ed32fe7..57814c5220b6db4b23c56d0078d9719f69b30e55 100644 --- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c +++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_common.c @@ -70,6 +70,7 @@ int parse_request_data(struct berval *req_val, struct extdom_req **_req) * requestType ENUMERATED { * simple (1), * full (2) + * full_with_groups (3) * }, * data InputData * } @@ -179,23 +180,23 @@ void free_req_data(struct extdom_req *req) free(req); } -int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req, - struct extdom_res **res) +int check_request(struct extdom_req *req, enum extdom_version version) +{ + if (version == EXTDOM_V0) { + if (req->request_type == REQ_FULL_WITH_GROUPS) { + return LDAP_PROTOCOL_ERROR; + } + } + + return LDAP_SUCCESS; +} + +static int get_buffer(size_t *_buf_len, char **_buf) { - int ret; - char *domain_name = NULL; - char *sid_str = NULL; - size_t buf_len; - char *buf = NULL; long pw_max; long gr_max; - struct pwd_grp pg_data; - struct passwd *pwd_result = NULL; - struct group *grp_result = NULL; - enum sss_id_type id_type; - char *fq_name = NULL; - char *sep; - + size_t buf_len; + char *buf; pw_max = sysconf(_SC_GETPW_R_SIZE_MAX); gr_max = sysconf(_SC_GETGR_R_SIZE_MAX); @@ -211,302 +212,631 @@ int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req, return LDAP_OPERATIONS_ERROR; } - switch (req->input_type) { - case INP_POSIX_UID: - if (req->request_type == REQ_SIMPLE) { - ret = sss_nss_getsidbyid(req->data.posix_uid.uid, &sid_str, - &id_type); - } else { - id_type = SSS_ID_TYPE_UID; - ret = getpwuid_r(req->data.posix_uid.uid, &pg_data.data.pwd, buf, - buf_len, &pwd_result); + *_buf_len = buf_len; + *_buf = buf; + + return LDAP_SUCCESS; +} + +static int get_user_grouplist(const char *name, gid_t gid, + size_t *_ngroups, gid_t **_groups ) +{ + int ret; + int ngroups; + gid_t *groups; + gid_t *new_groups; + + ngroups = 128; + groups = malloc(ngroups * sizeof(gid_t)); + if (groups == NULL) { + return LDAP_OPERATIONS_ERROR; + } + + ret = getgrouplist(name, gid, groups, &ngroups); + if (ret == -1) { + new_groups = realloc(groups, ngroups); + if (new_groups == NULL) { + free(groups); + return LDAP_OPERATIONS_ERROR; } + groups = new_groups; - domain_name = strdup(req->data.posix_uid.domain_name); - break; - case INP_POSIX_GID: - if (req->request_type == REQ_SIMPLE) { - ret = sss_nss_getsidbyid(req->data.posix_uid.uid, &sid_str, - &id_type); + ret = getgrouplist(name, gid, groups, &ngroups); + if (ret == -1) { + free(groups); + return LDAP_OPERATIONS_ERROR; + } + } + + *_ngroups = ngroups; + *_groups = groups; + + return LDAP_SUCCESS; +} + +static int pack_ber_sid(const char *sid, struct berval **berval) +{ + BerElement *ber = NULL; + int ret; + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_printf(ber,"{es}", RESP_SID, sid); + if (ret == -1) { + ber_free(ber, 1); + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_flatten(ber, berval); + ber_free(ber, 1); + if (ret == -1) { + return LDAP_OPERATIONS_ERROR; + } + + return LDAP_SUCCESS; +} + +#define SSSD_SYSDB_SID_STR "objectSIDString" + +static int pack_ber_user(enum response_types response_type, + const char *domain_name, const char *user_name, + uid_t uid, gid_t gid, + const char *gecos, const char *homedir, + const char *shell, const char *sid_str, + struct berval **berval) +{ + BerElement *ber = NULL; + int ret; + size_t ngroups; + gid_t *groups = NULL; + size_t buf_len; + char *buf = NULL; + struct group grp; + struct group *grp_result; + size_t c; + char *locat; + char *short_user_name = NULL; + const char *single_value_string_array[] = {NULL, NULL}; + + short_user_name = strdup(user_name); + if ((locat = strchr(short_user_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { + if (strcasecmp(locat+1, domain_name) == 0 ) { + locat[0] = '\0'; } else { - id_type = SSS_ID_TYPE_GID; - ret = getgrgid_r(req->data.posix_gid.gid, &pg_data.data.grp, buf, - buf_len, &grp_result); + ret = LDAP_NO_SUCH_OBJECT; + goto done; } + } - domain_name = strdup(req->data.posix_gid.domain_name); - break; - case INP_SID: - ret = sss_nss_getnamebysid(req->data.sid, &fq_name, &id_type); - if (ret != 0) { - ret = LDAP_OPERATIONS_ERROR; + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_printf(ber,"{e{ssii", response_type, domain_name, short_user_name, + uid, gid); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (response_type == RESP_USER_GROUPLIST) { + ret = get_user_grouplist(user_name, gid, &ngroups, &groups); + if (ret != LDAP_SUCCESS) { + goto done; + } + + ret = get_buffer(&buf_len, &buf); + if (ret != LDAP_SUCCESS) { goto done; } - sep = strrchr(fq_name, SSSD_DOMAIN_SEPARATOR); - if (sep == NULL) { + ret = ber_printf(ber,"sss", gecos, homedir, shell); + if (ret == -1) { ret = LDAP_OPERATIONS_ERROR; goto done; } - ret = asprintf(&domain_name, "%s", sep+1); + ret = ber_printf(ber,"{"); if (ret == -1) { ret = LDAP_OPERATIONS_ERROR; - domain_name = NULL; /* content is undefined according to - asprintf(3) */ - goto done; - } - - switch(id_type) { - case SSS_ID_TYPE_UID: - case SSS_ID_TYPE_BOTH: - ret = getpwnam_r(fq_name, &pg_data.data.pwd, buf, buf_len, - &pwd_result); - break; - case SSS_ID_TYPE_GID: - ret = getgrnam_r(fq_name, &pg_data.data.grp, buf, buf_len, - &grp_result); - break; - default: + goto done; + } + + for (c = 0; c < ngroups; c++) { + ret = getgrgid_r(groups[c], &grp, buf, buf_len, &grp_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + if (grp_result == NULL) { + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + + ret = ber_printf(ber, "s", grp.gr_name); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + } + + ret = ber_printf(ber,"}"); + if (ret == -1) { ret = LDAP_OPERATIONS_ERROR; goto done; } - break; - case INP_NAME: - ret = asprintf(&fq_name, "%s%c%s", req->data.name.object_name, - SSSD_DOMAIN_SEPARATOR, - req->data.name.domain_name); + + single_value_string_array[0] = sid_str; + ret = ber_printf(ber,"{{s{v}}}", SSSD_SYSDB_SID_STR, + single_value_string_array); if (ret == -1) { ret = LDAP_OPERATIONS_ERROR; - fq_name = NULL; /* content is undefined according to - asprintf(3) */ goto done; } + } + + ret = ber_printf(ber,"}}"); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = ber_flatten(ber, berval); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } - if (req->request_type == REQ_SIMPLE) { - ret = sss_nss_getsidbyname(fq_name, &sid_str, &id_type); + ret = LDAP_SUCCESS; +done: + free(short_user_name); + free(groups); + free(buf); + ber_free(ber, 1); + return ret; +} + +static int pack_ber_group(enum response_types response_type, + const char *domain_name, const char *group_name, + gid_t gid, char **members, const char *sid_str, + struct berval **berval) +{ + BerElement *ber = NULL; + int ret; + size_t c; + char *locat; + char *short_group_name = NULL; + const char *single_value_string_array[] = {NULL, NULL}; + + short_group_name = strdup(group_name); + if ((locat = strchr(short_group_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { + if (strcasecmp(locat+1, domain_name) == 0 ) { + locat[0] = '\0'; } else { - id_type = SSS_ID_TYPE_UID; - ret = getpwnam_r(fq_name, &pg_data.data.pwd, buf, buf_len, - &pwd_result); - if (ret == 0 && pwd_result == NULL) { /* no user entry found */ - id_type = SSS_ID_TYPE_GID; - ret = getgrnam_r(fq_name, &pg_data.data.grp, buf, buf_len, - &grp_result); + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + } + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_printf(ber,"{e{ssi", response_type, domain_name, short_group_name, + gid); + if (ret == -1) { + ber_free(ber, 1); + return LDAP_OPERATIONS_ERROR; + } + + if (response_type == RESP_GROUP_MEMBERS) { + ret = ber_printf(ber,"{"); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + for (c = 0; members[c] != NULL; c++) { + ret = ber_printf(ber, "s", members[c]); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; } } - domain_name = strdup(req->data.name.domain_name); + + ret = ber_printf(ber,"}"); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + single_value_string_array[0] = sid_str; + ret = ber_printf(ber,"{{s{v}}}", SSSD_SYSDB_SID_STR, + single_value_string_array); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + } + + ret = ber_printf(ber,"}}"); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = ber_flatten(ber, berval); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = LDAP_SUCCESS; + +done: + free(short_group_name); + ber_free(ber, 1); + return ret; +} + +static int pack_ber_name(const char *domain_name, const char *name, + struct berval **berval) +{ + BerElement *ber = NULL; + int ret; + + ber = ber_alloc_t( LBER_USE_DER ); + if (ber == NULL) { + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_printf(ber,"{e{ss}}", RESP_NAME, domain_name, name); + if (ret == -1) { + ber_free(ber, 1); + return LDAP_OPERATIONS_ERROR; + } + + ret = ber_flatten(ber, berval); + ber_free(ber, 1); + if (ret == -1) { + return LDAP_OPERATIONS_ERROR; + } + + return LDAP_SUCCESS; +} + +static int handle_uid_request(enum request_types request_type, uid_t uid, + const char *domain_name, struct berval **berval) +{ + int ret; + struct passwd pwd; + struct passwd *pwd_result = NULL; + char *sid_str = NULL; + enum sss_id_type id_type; + size_t buf_len; + char *buf = NULL; + + ret = get_buffer(&buf_len, &buf); + if (ret != LDAP_SUCCESS) { + return ret; + } + + if (request_type == REQ_SIMPLE || request_type == REQ_FULL_WITH_GROUPS) { + ret = sss_nss_getsidbyid(uid, &sid_str, &id_type); + if (ret != 0 || !(id_type == SSS_ID_TYPE_UID + || id_type == SSS_ID_TYPE_BOTH)) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; + } else { + ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } + } + + if (request_type == REQ_SIMPLE) { + ret = pack_ber_sid(sid_str, berval); + } else { + ret = getpwuid_r(uid, &pwd, buf, buf_len, &pwd_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + if (pwd_result == NULL) { + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + + ret = pack_ber_user((request_type == REQ_FULL ? RESP_USER + : RESP_USER_GROUPLIST), + domain_name, pwd.pw_name, pwd.pw_uid, + pwd.pw_gid, pwd.pw_gecos, pwd.pw_dir, + pwd.pw_shell, sid_str, berval); + } + +done: + free(sid_str); + free(buf); + return ret; +} + +static int handle_gid_request(enum request_types request_type, gid_t gid, + const char *domain_name, struct berval **berval) +{ + int ret; + struct group grp; + struct group *grp_result = NULL; + char *sid_str = NULL; + enum sss_id_type id_type; + size_t buf_len; + char *buf = NULL; + + ret = get_buffer(&buf_len, &buf); + if (ret != LDAP_SUCCESS) { + return ret; + } + + if (request_type == REQ_SIMPLE || request_type == REQ_FULL_WITH_GROUPS) { + ret = sss_nss_getsidbyid(gid, &sid_str, &id_type); + if (ret != 0 || id_type != SSS_ID_TYPE_GID) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; + } else { + ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } + } + + if (request_type == REQ_SIMPLE) { + ret = pack_ber_sid(sid_str, berval); + } else { + ret = getgrgid_r(gid, &grp, buf, buf_len, &grp_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + if (grp_result == NULL) { + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + + ret = pack_ber_group((request_type == REQ_FULL ? RESP_GROUP + : RESP_GROUP_MEMBERS), + domain_name, grp.gr_name, grp.gr_gid, + grp.gr_mem, sid_str, berval); + } + +done: + free(sid_str); + free(buf); + return ret; +} + +static int handle_sid_request(enum request_types request_type, const char *sid, + struct berval **berval) +{ + int ret; + struct passwd pwd; + struct passwd *pwd_result = NULL; + struct group grp; + struct group *grp_result = NULL; + char *domain_name = NULL; + char *fq_name = NULL; + char *object_name = NULL; + char *sep; + size_t buf_len; + char *buf = NULL; + enum sss_id_type id_type; + + ret = sss_nss_getnamebysid(sid, &fq_name, &id_type); + if (ret != 0) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; + } else { + ret = LDAP_OPERATIONS_ERROR; + } + goto done; + } + + sep = strchr(fq_name, SSSD_DOMAIN_SEPARATOR); + if (sep == NULL) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + object_name = strndup(fq_name, (sep - fq_name)); + domain_name = strdup(sep + 1); + if (object_name == NULL || domain_name == NULL) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (request_type == REQ_SIMPLE) { + ret = pack_ber_name(domain_name, object_name, berval); + goto done; + } + + ret = get_buffer(&buf_len, &buf); + if (ret != LDAP_SUCCESS) { + return ret; + } + + switch(id_type) { + case SSS_ID_TYPE_UID: + case SSS_ID_TYPE_BOTH: + ret = getpwnam_r(fq_name, &pwd, buf, buf_len, &pwd_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (pwd_result == NULL) { + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + + ret = pack_ber_user((request_type == REQ_FULL ? RESP_USER + : RESP_USER_GROUPLIST), + domain_name, pwd.pw_name, pwd.pw_uid, + pwd.pw_gid, pwd.pw_gecos, pwd.pw_dir, + pwd.pw_shell, sid, berval); + break; + case SSS_ID_TYPE_GID: + ret = getgrnam_r(fq_name, &grp, buf, buf_len, &grp_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (grp_result == NULL) { + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + + ret = pack_ber_group((request_type == REQ_FULL ? RESP_GROUP + : RESP_GROUP_MEMBERS), + domain_name, grp.gr_name, grp.gr_gid, + grp.gr_mem, sid, berval); break; default: - ret = LDAP_PROTOCOL_ERROR; - goto done; - } - - if (ret != 0) { - ret = LDAP_OPERATIONS_ERROR; - goto done; - } else if (ret == 0 && pwd_result == NULL && grp_result == NULL && - sid_str == NULL) { - ret = LDAP_NO_SUCH_OBJECT; - goto done; - } - - if (domain_name == NULL) { - ret = LDAP_OPERATIONS_ERROR; - goto done; - } - - ret = create_response(req, &pg_data, sid_str, id_type, domain_name, res); - if (ret != 0) { ret = LDAP_OPERATIONS_ERROR; goto done; } - - ret = LDAP_SUCCESS; - done: - free(buf); free(fq_name); + free(object_name); free(domain_name); - free(sid_str); + free(buf); return ret; } -int create_response(struct extdom_req *req, struct pwd_grp *pg_data, - const char *sid_str, enum sss_id_type id_type, - const char *domain_name, struct extdom_res **_res) +static int handle_name_request(enum request_types request_type, + const char *name, const char *domain_name, + struct berval **berval) { - int ret = EFAULT; - char *locat = NULL; - struct extdom_res *res; - - res = calloc(1, sizeof(struct extdom_res)); - if (res == NULL) { - return ENOMEM; + int ret; + char *fq_name = NULL; + struct passwd pwd; + struct passwd *pwd_result = NULL; + struct group grp; + struct group *grp_result = NULL; + char *sid_str = NULL; + enum sss_id_type id_type; + size_t buf_len; + char *buf = NULL; + + ret = asprintf(&fq_name, "%s%c%s", name, SSSD_DOMAIN_SEPARATOR, + domain_name); + if (ret == -1) { + ret = LDAP_OPERATIONS_ERROR; + fq_name = NULL; /* content is undefined according to + asprintf(3) */ + goto done; } - switch (req->request_type) { - case REQ_SIMPLE: - switch (req->input_type) { - case INP_SID: - res->response_type = RESP_NAME; - res->data.name.domain_name = strdup(domain_name); - switch(id_type) { - case SSS_ID_TYPE_UID: - case SSS_ID_TYPE_BOTH: - if ((locat = strchr(pg_data->data.pwd.pw_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { - if (strcasecmp(locat+1, domain_name) == 0 ) { - locat[0] = 0; - } else { - ret = LDAP_NO_SUCH_OBJECT; - goto done; - } - } - res->data.name.object_name = - strdup(pg_data->data.pwd.pw_name); - break; - case SSS_ID_TYPE_GID: - if ((locat = strchr(pg_data->data.grp.gr_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { - if (strcasecmp(locat+1, domain_name) == 0) { - locat[0] = 0; - } else { - ret = LDAP_NO_SUCH_OBJECT; - goto done; - } - } - res->data.name.object_name = - strdup(pg_data->data.grp.gr_name); - break; - default: - ret = EINVAL; - goto done; - } - - if (res->data.name.domain_name == NULL - || res->data.name.object_name == NULL) { - ret = ENOMEM; - goto done; - } - break; - case INP_NAME: - case INP_POSIX_UID: - case INP_POSIX_GID: - res->response_type = RESP_SID; - res->data.sid = strdup(sid_str); - if (res->data.sid == NULL) { - ret = ENOMEM; - goto done; - } - break; - default: - ret = EINVAL; - goto done; - } - break; - case REQ_FULL: - switch (id_type) { - case SSS_ID_TYPE_UID: - case SSS_ID_TYPE_BOTH: - res->response_type = RESP_USER; - res->data.user.domain_name = strdup(domain_name); - if ((locat = strchr(pg_data->data.pwd.pw_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { - if (strcasecmp(locat+1, domain_name) == 0) { - locat[0] = 0; - } else { - ret = LDAP_NO_SUCH_OBJECT; - goto done; - } - } - res->data.user.user_name = - strdup(pg_data->data.pwd.pw_name); - - if (res->data.user.domain_name == NULL - || res->data.user.user_name == NULL) { - ret = ENOMEM; - goto done; - } - - res->data.user.uid = pg_data->data.pwd.pw_uid; - res->data.user.gid = pg_data->data.pwd.pw_gid; - break; - case SSS_ID_TYPE_GID: - res->response_type = RESP_GROUP; - res->data.group.domain_name = strdup(domain_name); - if ((locat = strchr(pg_data->data.grp.gr_name, SSSD_DOMAIN_SEPARATOR)) != NULL) { - if (strcasecmp(locat+1, domain_name) == 0) { - locat[0] = 0; - } else { - ret = LDAP_NO_SUCH_OBJECT; - goto done; - } - } - res->data.group.group_name = - strdup(pg_data->data.grp.gr_name); - - if (res->data.group.domain_name == NULL - || res->data.group.group_name == NULL) { - ret = ENOMEM; - goto done; - } - - res->data.group.gid = pg_data->data.grp.gr_gid; - break; - default: - ret = EINVAL; - goto done; + if (request_type == REQ_SIMPLE || request_type == REQ_FULL_WITH_GROUPS) { + ret = sss_nss_getsidbyname(fq_name, &sid_str, &id_type); + if (ret != 0) { + if (ret == ENOENT) { + ret = LDAP_NO_SUCH_OBJECT; + } else { + ret = LDAP_OPERATIONS_ERROR; } - break; - default: - ret = EINVAL; - goto done; + goto done; + } } - ret = 0; - -done: - if (ret == 0) { - *_res = res; + if (request_type == REQ_SIMPLE) { + ret = pack_ber_sid(sid_str, berval); } else { - free_resp_data(res); - } + ret = get_buffer(&buf_len, &buf); + if (ret != LDAP_SUCCESS) { + goto done; + } + + ret = getpwnam_r(fq_name, &pwd, buf, buf_len, &pwd_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + if (pwd_result != NULL) { + ret = pack_ber_user((request_type == REQ_FULL ? RESP_USER + : RESP_USER_GROUPLIST), + domain_name, pwd.pw_name, pwd.pw_uid, + pwd.pw_gid, pwd.pw_gecos, pwd.pw_dir, + pwd.pw_shell, sid_str, berval); + } else { /* no user entry found */ + ret = getgrnam_r(fq_name, &grp, buf, buf_len, &grp_result); + if (ret != 0) { + ret = LDAP_OPERATIONS_ERROR; + goto done; + } - if (locat != NULL) { - locat[0] = SSSD_DOMAIN_SEPARATOR; + if (grp_result == NULL) { + ret = LDAP_NO_SUCH_OBJECT; + goto done; + } + + ret = pack_ber_group((request_type == REQ_FULL ? RESP_GROUP + : RESP_GROUP_MEMBERS), + domain_name, grp.gr_name, grp.gr_gid, + grp.gr_mem, sid_str, berval); + } } +done: + free(fq_name); + free(sid_str); + free(buf); + return ret; } -void free_resp_data(struct extdom_res *res) +int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req, + struct berval **berval) { - if (res == NULL) { - return; - } + int ret; + + switch (req->input_type) { + case INP_POSIX_UID: + ret = handle_uid_request(req->request_type, req->data.posix_uid.uid, + req->data.posix_uid.domain_name, berval); - switch (res->response_type) { - case RESP_SID: - free(res->data.sid); break; - case RESP_NAME: - free(res->data.name.domain_name); - free(res->data.name.object_name); + case INP_POSIX_GID: + ret = handle_gid_request(req->request_type, req->data.posix_gid.gid, + req->data.posix_uid.domain_name, berval); + break; - case RESP_USER: - free(res->data.user.domain_name); - free(res->data.user.user_name); + case INP_SID: + ret = handle_sid_request(req->request_type, req->data.sid, berval); break; - case RESP_GROUP: - free(res->data.group.domain_name); - free(res->data.group.group_name); + case INP_NAME: + ret = handle_name_request(req->request_type, req->data.name.object_name, + req->data.name.domain_name, berval); + break; + default: + ret = LDAP_PROTOCOL_ERROR; + goto done; } - free(res); + +done: + + return ret; } - int pack_response(struct extdom_res *res, struct berval **ret_val) { BerElement *ber = NULL; diff --git a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c index 9315da260ee3de660ea8ff708950945110da37e3..aa66c145bc6cf2b77fdfe37be18da67588dc0439 100644 --- a/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c +++ b/daemons/ipa-slapi-plugins/ipa-extdom-extop/ipa_extdom_extop.c @@ -49,6 +49,7 @@ Slapi_PluginDesc ipa_extdom_plugin_desc = { static char *ipa_extdom_oid_list[] = { EXOP_EXTDOM_OID, + EXOP_EXTDOM_V1_OID, NULL }; @@ -71,8 +72,8 @@ static int ipa_extdom_extop(Slapi_PBlock *pb) struct berval *req_val = NULL; struct berval *ret_val = NULL; struct extdom_req *req = NULL; - struct extdom_res *res = NULL; struct ipa_extdom_ctx *ctx; + enum extdom_version version; ret = slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &oid); if (ret != 0) { @@ -82,7 +83,11 @@ static int ipa_extdom_extop(Slapi_PBlock *pb) } LOG("Received extended operation request with OID %s\n", oid); - if (strcasecmp(oid, EXOP_EXTDOM_OID) != 0) { + if (strcasecmp(oid, EXOP_EXTDOM_OID) == 0) { + version = EXTDOM_V0; + } else if (strcasecmp(oid, EXOP_EXTDOM_V1_OID) == 0) { + version = EXTDOM_V1; + } else { return SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; } @@ -107,21 +112,21 @@ static int ipa_extdom_extop(Slapi_PBlock *pb) goto done; } - ret = handle_request(ctx, req, &res); + ret = check_request(req, version); + if (ret != LDAP_SUCCESS) { + rc = LDAP_UNWILLING_TO_PERFORM; + err_msg = "Error in request data.\n"; + goto done; + } + + ret = handle_request(ctx, req, &ret_val); if (ret != LDAP_SUCCESS) { rc = LDAP_OPERATIONS_ERROR; err_msg = "Failed to handle the request.\n"; goto done; } - ret = pack_response(res, &ret_val); - if (ret != LDAP_SUCCESS) { - rc = LDAP_OPERATIONS_ERROR; - err_msg = "Failed to pack the response.\n"; - goto done; - } - - ret = slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, EXOP_EXTDOM_OID); + ret = slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, oid); if (ret != 0) { rc = LDAP_OPERATIONS_ERROR; err_msg = "Failed to set the OID for the response.\n"; @@ -139,7 +144,6 @@ static int ipa_extdom_extop(Slapi_PBlock *pb) done: free_req_data(req); - free_resp_data(res); if (err_msg != NULL) { LOG("%s", err_msg); } -- 1.8.5.3
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel