/** BEGIN COPYRIGHT BLOCK
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Additional permission under GPLv3 section 7:
 *
 * In the following paragraph, "GPL" means the GNU General Public
 * License, version 3 or any later version, and "Non-GPL Code" means
 * code that is governed neither by the GPL nor a license
 * compatible with the GPL.
 *
 * You may link the code of this Program with Non-GPL Code and convey
 * linked combinations including the two, provided that such Non-GPL
 * Code only links to the code of this Program through those well
 * defined interfaces identified in the file named EXCEPTION found in
 * the source code files (the "Approved Interfaces"). The files of
 * Non-GPL Code may instantiate templates or use macros or inline
 * functions from the Approved Interfaces without causing the resulting
 * work to be covered by the GPL. Only the copyright holders of this
 * Program may make changes or additions to the list of Approved
 * Interfaces.
 *
 * Authors:
 * Sumit Bose <sbose@redhat.com>
 *
 * Copyright (C) 2011 Red Hat, Inc.
 * All rights reserved.
 * END COPYRIGHT BLOCK **/

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1 /* for asprintf() */
#endif

#include <errno.h>
#include <stdio.h>
#include <sys/param.h>

#include "ipa_extdom.h"
#include "back_extdom.h"
#include "util.h"

#define SSSD_DOMAIN_SEPARATOR '@'

int get_buffer(size_t *_buf_len, char **_buf)
{
    long pw_max;
    long gr_max;
    size_t buf_len;
    char *buf;

    pw_max = sysconf(_SC_GETPW_R_SIZE_MAX);
    gr_max = sysconf(_SC_GETGR_R_SIZE_MAX);

    buf_len = MAX(16384, MAX(pw_max, gr_max));

    buf = malloc(sizeof(char) * buf_len);
    if (buf == NULL) {
        return LDAP_OPERATIONS_ERROR;
    }

    *_buf_len = buf_len;
    *_buf = buf;

    return LDAP_SUCCESS;
}

static int inc_buffer(size_t buf_max, size_t *_buf_len, char **_buf)
{
    size_t tmp_len;
    char *tmp_buf;

    tmp_buf = *_buf;
    tmp_len = *_buf_len;

    tmp_len *= 2;
    if (tmp_len > buf_max) {
        return ERANGE;
    }

    tmp_buf = realloc(tmp_buf, tmp_len);
    if (tmp_buf == NULL) {
        return ENOMEM;
    }

    *_buf_len = tmp_len;
    *_buf = tmp_buf;

    return 0;
}

int __nss_to_err(enum nss_status errcode)
{
    switch(errcode) {
    case NSS_STATUS_SUCCESS:
        return 0;
    case NSS_STATUS_NOTFOUND:
        return ENOENT;
    case NSS_STATUS_TRYAGAIN:
        return ERANGE;
    case NSS_STATUS_UNAVAIL:
        return ETIMEDOUT;
    default:
        return -1;
    }
}

int getpwnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
                       struct passwd *pwd, char **buf, size_t *buf_len)
{
    int ret, lerrno = 0;
    struct passwd *result = NULL;
    enum nss_status rc;

    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
        rc = back_extdom_getpwnam(ctx->nss_ctx, name, pwd, *buf, *buf_len, &result, &lerrno);
        ret = __nss_to_err(rc);
        if (ret == ERANGE) {
            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
            if (ret != 0) goto done;
        }
    }

done:
    switch(ret) {
    case 0:
        if (result == NULL) ret = ENOENT;
        break;
    case ERANGE:
        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
    default:
        break;
    }
    return ret;
}

int getpwuid_r_wrapper(struct ipa_extdom_ctx *ctx, uid_t uid,
                       struct passwd *pwd, char **buf, size_t *buf_len)
{
    int ret, lerrno;
    struct passwd *result = NULL;
    enum nss_status rc;

    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
        rc = back_extdom_getpwuid(ctx->nss_ctx, uid, pwd, *buf, *buf_len, &result, &lerrno);
        ret = __nss_to_err(rc);
        if (ret == ERANGE) {
            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
            if (ret != 0) goto done;
        }
    }

done:
    switch(ret) {
    case 0:
        if (result == NULL) ret = ENOENT;
        break;
    case ERANGE:
        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
    default:
        break;
    }

    return ret;
}

int getgrnam_r_wrapper(struct ipa_extdom_ctx *ctx, const char *name,
                       struct group *grp, char **buf, size_t *buf_len)
{
    int ret, lerrno;
    struct group *result = NULL;
    enum nss_status rc;

    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
        rc = back_extdom_getgrnam(ctx->nss_ctx, name, grp, *buf, *buf_len, &result, &lerrno);
        ret = __nss_to_err(rc);
        if (ret == ERANGE) {
            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
            if (ret != 0) goto done;
        }
    }

done:
    switch(ret) {
    case 0:
        if (result == NULL) ret = ENOENT;
        break;
    case ERANGE:
        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
    default:
        break;
    }

    return ret;
}

int getgrgid_r_wrapper(struct ipa_extdom_ctx *ctx, gid_t gid,
                       struct group *grp, char **buf, size_t *buf_len)
{
    int ret, lerrno;
    struct group *result = NULL;
    enum nss_status rc;

    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
        rc = back_extdom_getgrgid(ctx->nss_ctx, gid, grp, *buf, *buf_len, &result, &lerrno);
        ret = __nss_to_err(rc);
        if (ret == ERANGE) {
            ret = inc_buffer(ctx->max_nss_buf_size, buf_len, buf);
            if (ret != 0) goto done;
        }
    }

done:
    switch(ret) {
    case 0:
        if (result == NULL) ret = ENOENT;
        break;
    case ERANGE:
        LOG("Buffer too small, increase ipaExtdomMaxNssBufSize.\n");
    default:
        break;
    }

    return ret;
}

void set_err_msg(struct extdom_req *req, const char *format, ...)
{
    int ret;
    va_list ap;

    if (req == NULL) {
        return;
    }

    if (format == NULL || req->err_msg != NULL) {
        /* Do not override an existing error message. */
        return;
    }
    va_start(ap, format);

    ret = vasprintf(&req->err_msg, format, ap);
    if (ret == -1) {
        req->err_msg = strdup("vasprintf failed.\n");
    }

    va_end(ap);
}

int parse_request_data(struct berval *req_val, struct extdom_req **_req)
{
    BerElement *ber = NULL;
    ber_tag_t tag;
    ber_int_t input_type;
    ber_int_t request_type;
    ber_int_t id;
    struct extdom_req *req;

/* We expect the following request:
 * ExtdomRequestValue ::= SEQUENCE {
 *    inputType ENUMERATED {
 *        sid (1),
 *        name (2),
 *        posix uid (3),
 *        posix gid (3)
 *    },
 *    requestType ENUMERATED {
 *        simple (1),
 *        full (2)
 *        full_with_groups (3)
 *    },
 *    data InputData
 * }
 *
 * InputData ::= CHOICE {
 *    sid OCTET STRING,
 *    name NameDomainData
 *    uid PosixUid,
 *    gid PosixGid
 * }
 *
 * NameDomainData ::= SEQUENCE {
 *    domain_name OCTET STRING,
 *    object_name OCTET STRING
 * }
 *
 * PosixUid ::= SEQUENCE {
 *    domain_name OCTET STRING,
 *    uid INTEGER
 * }
 *
 * PosixGid ::= SEQUENCE {
 *    domain_name OCTET STRING,
 *    gid INTEGER
 * }
 */

    req = calloc(sizeof(struct extdom_req), 1);
    if (req == NULL) {
        /* Since we return req even in the case of an error we make sure it is
         * always safe to call free_req_data() on the returned data. */
        *_req = NULL;
        return LDAP_OPERATIONS_ERROR;
    }

    *_req = req;

    if (req_val == NULL || req_val->bv_val == NULL || req_val->bv_len == 0) {
        set_err_msg(req, "Missing request data");
        return LDAP_PROTOCOL_ERROR;
    }

    ber = ber_init(req_val);
    if (ber == NULL) {
        set_err_msg(req, "Cannot initialize BER struct");
        return LDAP_PROTOCOL_ERROR;
    }

    tag = ber_scanf(ber, "{ee", &input_type, &request_type);
    if (tag == LBER_ERROR) {
        ber_free(ber, 1);
        set_err_msg(req, "Cannot read input and request type");
        return LDAP_PROTOCOL_ERROR;
    }

    req->input_type = input_type;
    req->request_type = request_type;

    switch (req->input_type) {
        case INP_NAME:
            tag = ber_scanf(ber, "{aa}}", &req->data.name.domain_name,
                                            &req->data.name.object_name);
            break;
        case INP_SID:
            tag = ber_scanf(ber, "a}", &req->data.sid);
            break;
        case INP_POSIX_UID:
            tag = ber_scanf(ber, "{ai}}", &req->data.posix_uid.domain_name,
                                            &id);
            req->data.posix_uid.uid = (uid_t) id;
            break;
        case INP_POSIX_GID:
            tag = ber_scanf(ber, "{ai}}", &req->data.posix_gid.domain_name,
                                            &id);
            req->data.posix_gid.gid = (gid_t) id;
            break;
        case INP_CERT:
            tag = ber_scanf(ber, "a}", &req->data.cert);
            break;
        default:
            ber_free(ber, 1);
            set_err_msg(req, "Unknown input type");
            return LDAP_PROTOCOL_ERROR;
    }
    ber_free(ber, 1);
    if (tag == LBER_ERROR) {
        set_err_msg(req, "Failed to decode BER data");
        return LDAP_PROTOCOL_ERROR;
    }

    return LDAP_SUCCESS;
}

void free_req_data(struct extdom_req *req)
{
    if (req == NULL) {
        return;
    }

    switch (req->input_type) {
    case INP_NAME:
        ber_memfree(req->data.name.domain_name);
        ber_memfree(req->data.name.object_name);
        break;
    case INP_SID:
        ber_memfree(req->data.sid);
        break;
    case INP_POSIX_UID:
        ber_memfree(req->data.posix_uid.domain_name);
        break;
    case INP_POSIX_GID:
        ber_memfree(req->data.posix_gid.domain_name);
        break;
    case INP_CERT:
        ber_memfree(req->data.cert);
        break;
    }

    free(req->err_msg);
    free(req);
}

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;
}

int get_user_grouplist(struct ipa_extdom_ctx *ctx, const char *name, gid_t gid,
                       size_t *_ngroups, gid_t **_groups)
{
    int lerrno;
    int ngroups;
    gid_t *groups;
    gid_t *new_groups;
    enum nss_status rc;

    ngroups = 128;
    groups = malloc(ngroups * sizeof(gid_t));
    if (groups == NULL) {
        return LDAP_OPERATIONS_ERROR;
    }

    for(rc = NSS_STATUS_TRYAGAIN; rc == NSS_STATUS_TRYAGAIN;) {
        rc = back_extdom_getgrouplist(ctx->nss_ctx, name, gid, groups, &ngroups, &lerrno);
        if (rc == NSS_STATUS_TRYAGAIN) {
            new_groups = NULL;
            if (lerrno == ERANGE) {
                new_groups = realloc(groups, ngroups * sizeof(gid_t));
            }
            if ((new_groups == NULL) || (lerrno == ENOMEM)) {
                free(groups);
                return LDAP_OPERATIONS_ERROR;
            }
            groups = new_groups;
        }
    }

    *_ngroups = ngroups;
    *_groups = groups;

    return LDAP_SUCCESS;
}

static int add_kv_list(BerElement *ber, struct sss_nss_kv *kv_list)
{
    size_t c;
    int ret;
    const char *single_value_string_array[] = {NULL, NULL};

    ret = ber_printf(ber,"{");
    if (ret == -1) {
        return LDAP_OPERATIONS_ERROR;
    }

    for (c = 0; kv_list[c].key != NULL; c++) {
        single_value_string_array[0] = kv_list[c].value;
        ret = ber_printf(ber,"{s{v}}", kv_list[c].key,
                                       single_value_string_array);
        if (ret == -1) {
            return LDAP_OPERATIONS_ERROR;
        }
    }

    ret = ber_printf(ber,"}");
    if (ret == -1) {
        return LDAP_OPERATIONS_ERROR;
    }

    return LDAP_SUCCESS;
}

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;
}

int pack_ber_user(struct ipa_extdom_ctx *ctx,
                  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, struct sss_nss_kv *kv_list,
                  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;
    size_t c;
    char *locat;
    char *short_user_name = NULL;

    short_user_name = strdup(user_name);
    if ((locat = strrchr(short_user_name, SSSD_DOMAIN_SEPARATOR)) != NULL) {
        if (strcasecmp(locat+1, domain_name) == 0  ) {
            locat[0] = '\0';
        } else {
            ret = LDAP_NO_SUCH_OBJECT;
            goto done;
        }
    }

    ber = ber_alloc_t( LBER_USE_DER );
    if (ber == NULL) {
        ret = LDAP_OPERATIONS_ERROR;
        goto done;
    }

    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(ctx, 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_wrapper(ctx,
                                     groups[c], &grp, &buf, &buf_len);
            if (ret != 0) {
                if (ret == ENOMEM || ret == ERANGE) {
                    ret = LDAP_OPERATIONS_ERROR;
                } else {
                    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;
        }

        if (kv_list != NULL) {
            ret = add_kv_list(ber, kv_list);
            if (ret != LDAP_SUCCESS) {
                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;
}

int pack_ber_group(enum response_types response_type,
                   const char *domain_name, const char *group_name,
                   gid_t gid, char **members, struct sss_nss_kv *kv_list,
                   struct berval **berval)
{
    BerElement *ber = NULL;
    int ret;
    size_t c;
    char *locat;
    char *short_group_name = NULL;

    short_group_name = strdup(group_name);
    if ((locat = strrchr(short_group_name, SSSD_DOMAIN_SEPARATOR)) != NULL) {
        if (strcasecmp(locat+1, domain_name) == 0  ) {
            locat[0] = '\0';
        } else {
            ret = LDAP_NO_SUCH_OBJECT;
            goto done;
        }
    }

    ber = ber_alloc_t( LBER_USE_DER );
    if (ber == NULL) {
        ret = LDAP_OPERATIONS_ERROR;
        goto done;
    }

    ret = ber_printf(ber,"{e{ssi", response_type, domain_name, short_group_name,
                                   gid);
    if (ret == -1) {
        ret = LDAP_OPERATIONS_ERROR;
        goto done;
    }

    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;
            }
        }

        ret = ber_printf(ber,"}");
        if (ret == -1) {
            ret = LDAP_OPERATIONS_ERROR;
            goto done;
        }

        if (kv_list != NULL) {
            ret = add_kv_list(ber, kv_list);
            if (ret != LDAP_SUCCESS) {
                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;
}

int pack_ber_name_list(struct extdom_req *req, char **fq_name_list,
                       struct berval **berval)
{
    BerElement *ber = NULL;
    int ret;
    char *sep;
    size_t c;
    size_t len;
    size_t name_len;

    /* count the names */
    for (c = 0; fq_name_list[c] != NULL; c++);
    if (c == 0) {
        set_err_msg(req, "Empty name list");
        return LDAP_NO_SUCH_OBJECT;
    }

    ber = ber_alloc_t( LBER_USE_DER );
    if (ber == NULL) {
        set_err_msg(req, "BER alloc failed");
        return LDAP_OPERATIONS_ERROR;
    }


    ret = ber_printf(ber,"{e{", RESP_NAME_LIST);
    if (ret == -1) {
        set_err_msg(req, "BER start failed");
        ber_free(ber, 1);
        return LDAP_OPERATIONS_ERROR;
    }

    for (c = 0; fq_name_list[c] != NULL; c++) {
        len = strlen(fq_name_list[c]);
        if (len < 3) {
            set_err_msg(req, "Fully qualified name too short");
            ber_free(ber, 1);
            return LDAP_OPERATIONS_ERROR;
        }

        sep = strrchr(fq_name_list[c], SSSD_DOMAIN_SEPARATOR);
        if (sep == NULL) {
            set_err_msg(req, "Failed to split fully qualified name");
            ber_free(ber, 1);
            return LDAP_OPERATIONS_ERROR;
        }

        name_len = sep - fq_name_list[c];
        if (name_len == 0) {
            set_err_msg(req, "Missing name.");
            ber_free(ber, 1);
            return LDAP_OPERATIONS_ERROR;
        }
        if (name_len + 1 == len) {
            set_err_msg(req, "Missing domain.");
            ber_free(ber, 1);
            return LDAP_OPERATIONS_ERROR;
        }

        ret = ber_printf(ber,"{oo}", (sep + 1),  len - name_len -1,
                                      fq_name_list[c], name_len);
        if (ret == -1) {
        set_err_msg(req, "BER list item failed");
            ber_free(ber, 1);
            return LDAP_OPERATIONS_ERROR;
        }
    }

    ret = ber_printf(ber,"}}");
    if (ret == -1) {
        set_err_msg(req, "BER end failed");
        ber_free(ber, 1);
        return LDAP_OPERATIONS_ERROR;
    }

    ret = ber_flatten(ber, berval);
    ber_free(ber, 1);
    if (ret == -1) {
        set_err_msg(req, "BER flatten failed");
        return LDAP_OPERATIONS_ERROR;
    }

    return LDAP_SUCCESS;
}

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(struct ipa_extdom_ctx *ctx,
                              struct extdom_req *req,
                              enum request_types request_type, uid_t uid,
                              const char *domain_name, struct berval **berval)
{
    int ret;
    struct passwd pwd;
    char *sid_str = NULL;
    enum sss_id_type id_type;
    size_t buf_len;
    char *buf = NULL;
    struct sss_nss_kv *kv_list = NULL;

    ret = get_buffer(&buf_len, &buf);
    if (ret != LDAP_SUCCESS) {
        return ret;
    }

    if (request_type == REQ_SIMPLE) {
        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 {
                set_err_msg(req, "Failed to lookup SID by UID");
                ret = LDAP_OPERATIONS_ERROR;
            }
            goto done;
        }

        ret = pack_ber_sid(sid_str, berval);
    } else {
        ret = getpwuid_r_wrapper(ctx, uid, &pwd, &buf, &buf_len);
        if (ret != 0) {
            if (ret == ENOMEM || ret == ERANGE) {
                ret = LDAP_OPERATIONS_ERROR;
            } else {
                ret = LDAP_NO_SUCH_OBJECT;
            }
            goto done;
        }

        if (request_type == REQ_FULL_WITH_GROUPS) {
            ret = sss_nss_getorigbyname(pwd.pw_name, &kv_list, &id_type);
            if (ret != 0 || !(id_type == SSS_ID_TYPE_UID
                                || id_type == SSS_ID_TYPE_BOTH)) {
                set_err_msg(req, "Failed to read original data");
                if (ret == ENOENT) {
                    ret = LDAP_NO_SUCH_OBJECT;
                } else {
                    ret = LDAP_OPERATIONS_ERROR;
                }
                goto done;
            }
        }

        ret = pack_ber_user(ctx,
                            (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, kv_list, berval);
    }

done:
    sss_nss_free_kv(kv_list);
    free(sid_str);
    free(buf);
    return ret;
}

static int handle_gid_request(struct ipa_extdom_ctx *ctx,
                              struct extdom_req *req,
                              enum request_types request_type, gid_t gid,
                              const char *domain_name, struct berval **berval)
{
    int ret;
    struct group grp;
    char *sid_str = NULL;
    enum sss_id_type id_type;
    size_t buf_len;
    char *buf = NULL;
    struct sss_nss_kv *kv_list = NULL;

    ret = get_buffer(&buf_len, &buf);
    if (ret != LDAP_SUCCESS) {
        return ret;
    }

    if (request_type == REQ_SIMPLE) {
        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 {
                set_err_msg(req, "Failed to lookup SID by GID");
                ret = LDAP_OPERATIONS_ERROR;
            }
            goto done;
        }

        ret = pack_ber_sid(sid_str, berval);
    } else {
        ret = getgrgid_r_wrapper(ctx, gid, &grp, &buf, &buf_len);
        if (ret != 0) {
            if (ret == ENOMEM || ret == ERANGE) {
                ret = LDAP_OPERATIONS_ERROR;
            } else {
                ret = LDAP_NO_SUCH_OBJECT;
            }
            goto done;
        }

        if (request_type == REQ_FULL_WITH_GROUPS) {
            ret = sss_nss_getorigbyname(grp.gr_name, &kv_list, &id_type);
            if (ret != 0 || !(id_type == SSS_ID_TYPE_GID
                                || id_type == SSS_ID_TYPE_BOTH)) {
                set_err_msg(req, "Failed to read original data");
                if (ret == ENOENT) {
                    ret = LDAP_NO_SUCH_OBJECT;
                } else {
                    ret = LDAP_OPERATIONS_ERROR;
                }
                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, kv_list, berval);
    }

done:
    sss_nss_free_kv(kv_list);
    free(sid_str);
    free(buf);
    return ret;
}

static int handle_cert_request(struct ipa_extdom_ctx *ctx,
                               struct extdom_req *req,
                               enum request_types request_type,
                               enum input_types input_type,
                               const char *input,
                               struct berval **berval)
{
    int ret;
    char **fq_names = NULL;
    enum sss_id_type *id_types = NULL;
    size_t c;

    if (request_type != REQ_SIMPLE) {
        set_err_msg(req, "Only simple request type allowed "
                         "for lookups by certificate");
        ret = LDAP_PROTOCOL_ERROR;
        goto done;
    }

    ret = sss_nss_getlistbycert(input, &fq_names, &id_types);
    if (ret != 0) {
        if (ret == ENOENT) {
            ret = LDAP_NO_SUCH_OBJECT;
        } else {
            set_err_msg(req, "Failed to lookup name by certificate");
            ret = LDAP_OPERATIONS_ERROR;
        }
        goto done;
    }

    ret = pack_ber_name_list(req, fq_names, berval);

done:
    if (fq_names != NULL) {
        for (c = 0; fq_names[c] != NULL; c++) {
            free(fq_names[c]);
        }
        free(fq_names);
    }
    free(id_types);

    return ret;
}

static int handle_sid_request(struct ipa_extdom_ctx *ctx,
                              struct extdom_req *req,
                              enum request_types request_type,
                              enum input_types input_type,
                              const char *input,
                              struct berval **berval)
{
    int ret;
    struct passwd pwd;
    struct group grp;
    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;
    struct sss_nss_kv *kv_list = NULL;

    ret = sss_nss_getnamebysid(input, &fq_name, &id_type);
    if (ret != 0) {
        if (ret == ENOENT) {
            ret = LDAP_NO_SUCH_OBJECT;
        } else {
            set_err_msg(req, "Failed to lookup name by SID");
            ret = LDAP_OPERATIONS_ERROR;
        }
        goto done;
    }

    sep = strrchr(fq_name, SSSD_DOMAIN_SEPARATOR);
    if (sep == NULL) {
        set_err_msg(req, "Failed to split fully qualified name");
        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) {
        set_err_msg(req, "Missing name or domain");
        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) {
        goto done;
    }

    switch(id_type) {
    case SSS_ID_TYPE_UID:
    case SSS_ID_TYPE_BOTH:
        ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len);
        if (ret != 0) {
            if (ret == ENOMEM || ret == ERANGE) {
                ret = LDAP_OPERATIONS_ERROR;
            } else {
                ret = LDAP_NO_SUCH_OBJECT;
            }
            goto done;
        }

        if (request_type == REQ_FULL_WITH_GROUPS) {
            ret = sss_nss_getorigbyname(pwd.pw_name, &kv_list, &id_type);
            if (ret != 0 || !(id_type == SSS_ID_TYPE_UID
                                || id_type == SSS_ID_TYPE_BOTH)) {
                set_err_msg(req, "Failed to read original data");
                if (ret == ENOENT) {
                    ret = LDAP_NO_SUCH_OBJECT;
                } else {
                    ret = LDAP_OPERATIONS_ERROR;
                }
                goto done;
            }
        }

        ret = pack_ber_user(ctx,
                            (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, kv_list, berval);
        break;
    case SSS_ID_TYPE_GID:
        ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len);
        if (ret != 0) {
            if (ret == ENOMEM || ret == ERANGE) {
                ret = LDAP_OPERATIONS_ERROR;
            } else {
                ret = LDAP_NO_SUCH_OBJECT;
            }
            goto done;
        }

        if (request_type == REQ_FULL_WITH_GROUPS) {
            ret = sss_nss_getorigbyname(grp.gr_name, &kv_list, &id_type);
            if (ret != 0 || !(id_type == SSS_ID_TYPE_GID
                                || id_type == SSS_ID_TYPE_BOTH)) {
                set_err_msg(req, "Failed to read original data");
                if (ret == ENOENT) {
                    ret = LDAP_NO_SUCH_OBJECT;
                } else {
                    ret = LDAP_OPERATIONS_ERROR;
                }
                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, kv_list, berval);
        break;
    default:
        ret = LDAP_OPERATIONS_ERROR;
        goto done;
    }

done:
    sss_nss_free_kv(kv_list);
    free(fq_name);
    free(object_name);
    free(domain_name);
    free(buf);

    return ret;
}

static int handle_name_request(struct ipa_extdom_ctx *ctx,
                               struct extdom_req *req,
                               enum request_types request_type,
                               const char *name, const char *domain_name,
                               struct berval **berval)
{
    int ret;
    char *fq_name = NULL;
    struct passwd pwd;
    struct group grp;
    char *sid_str = NULL;
    enum sss_id_type id_type;
    size_t buf_len;
    char *buf = NULL;
    struct sss_nss_kv *kv_list = NULL;

    if (strchr(name, SSSD_DOMAIN_SEPARATOR) == NULL) {
        ret = asprintf(&fq_name, "%s%c%s", name, SSSD_DOMAIN_SEPARATOR,
                                           domain_name);
    } else {
        /* SSSD_DOMAIN_SEPARATOR already present, assume UPN */
        ret = asprintf(&fq_name, "%s", name);
    }
    if (ret == -1) {
        ret = LDAP_OPERATIONS_ERROR;
        set_err_msg(req, "Failed to create fully qualified name");
        fq_name = NULL; /* content is undefined according to
                           asprintf(3) */
        goto done;
    }

    if (request_type == REQ_SIMPLE) {
        ret = sss_nss_getsidbyname(fq_name, &sid_str, &id_type);
        if (ret != 0) {
            if (ret == ENOENT) {
                ret = LDAP_NO_SUCH_OBJECT;
            } else {
                set_err_msg(req, "Failed to lookup SID by name");
                ret = LDAP_OPERATIONS_ERROR;
            }
            goto done;
        }

        ret = pack_ber_sid(sid_str, berval);
    } else {
        ret = get_buffer(&buf_len, &buf);
        if (ret != LDAP_SUCCESS) {
            goto done;
        }

        ret = getpwnam_r_wrapper(ctx, fq_name, &pwd, &buf, &buf_len);
        if (ret == 0) {
            if (request_type == REQ_FULL_WITH_GROUPS) {
                ret = sss_nss_getorigbyname(pwd.pw_name, &kv_list, &id_type);
                if (ret != 0 || !(id_type == SSS_ID_TYPE_UID
                                    || id_type == SSS_ID_TYPE_BOTH)) {
                    set_err_msg(req, "Failed to read original data");
                    if (ret == ENOENT) {
                        ret = LDAP_NO_SUCH_OBJECT;
                    } else {
                        ret = LDAP_OPERATIONS_ERROR;
                    }
                    goto done;
                }
            }
            ret = pack_ber_user(ctx,
                                (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, kv_list, berval);
        } else if (ret == ENOMEM || ret == ERANGE) {
            ret = LDAP_OPERATIONS_ERROR;
            goto done;
        } else { /* no user entry found */
            /* according to the getpwnam() man page there are a couple of
             * error codes which can indicate that the user was not found. To
             * be on the safe side we fail back to the group lookup on all
             * errors. */
            ret = getgrnam_r_wrapper(ctx, fq_name, &grp, &buf, &buf_len);
            if (ret != 0) {
                if (ret == ENOMEM || ret == ERANGE) {
                    ret = LDAP_OPERATIONS_ERROR;
                } else {
                    ret = LDAP_NO_SUCH_OBJECT;
                }
                goto done;
            }

            if (request_type == REQ_FULL_WITH_GROUPS) {
                ret = sss_nss_getorigbyname(grp.gr_name, &kv_list, &id_type);
                if (ret != 0 || !(id_type == SSS_ID_TYPE_GID
                                    || id_type == SSS_ID_TYPE_BOTH)) {
                    if (ret == ENOENT) {
                        ret = LDAP_NO_SUCH_OBJECT;
                    } else {
                        set_err_msg(req, "Failed to read original data");
                        ret = LDAP_OPERATIONS_ERROR;
                    }
                    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, kv_list, berval);
        }
    }

done:
    sss_nss_free_kv(kv_list);
    free(fq_name);
    free(sid_str);
    free(buf);

    return ret;
}

int handle_request(struct ipa_extdom_ctx *ctx, struct extdom_req *req,
                   struct berval **berval)
{
    int ret;

    switch (req->input_type) {
    case INP_POSIX_UID:
        ret = handle_uid_request(ctx, req, req->request_type,
                                 req->data.posix_uid.uid,
                                 req->data.posix_uid.domain_name, berval);

        break;
    case INP_POSIX_GID:
        ret = handle_gid_request(ctx, req, req->request_type,
                                 req->data.posix_gid.gid,
                                 req->data.posix_uid.domain_name, berval);

        break;
    case INP_SID:
        ret = handle_sid_request(ctx, req, req->request_type,
                                 req->input_type, req->data.sid, berval);
        break;
    case INP_CERT:
        ret = handle_cert_request(ctx, req, req->request_type,
                                  req->input_type, req->data.cert, berval);
        break;
    case INP_NAME:
        ret = handle_name_request(ctx, req, req->request_type,
                                  req->data.name.object_name,
                                  req->data.name.domain_name, berval);

        break;
    default:
        set_err_msg(req, "Unknown input type");
        ret = LDAP_PROTOCOL_ERROR;
        goto done;
    }


done:

    return ret;
}

int pack_response(struct extdom_res *res, struct berval **ret_val)
{
    BerElement *ber = NULL;
    int ret;

/* We send to follwing response:
 * ExtdomResponseValue ::= SEQUENCE {
 *    responseType ENUMERATED {
 *        sid (1),
 *        name (2),
 *        posix_user (3),
 *        posix_group (4)
 *    },
 *    data OutputData
 * }
 *
 * OutputData ::= CHOICE {
 *    sid OCTET STRING,
 *    name NameDomainData,
 *    user PosixUser,
 *    group PosixGroup
 * }
 *
 * NameDomainData ::= SEQUENCE {
 *    domain_name OCTET STRING,
 *    object_name OCTET STRING
 * }
 *
 * PosixUser ::= SEQUENCE {
 *    domain_name OCTET STRING,
 *    user_name OCTET STRING,
 *    uid INTEGER
 *    gid INTEGER
 * }
 *
 * PosixGroup ::= SEQUENCE {
 *    domain_name OCTET STRING,
 *    group_name OCTET STRING,
 *    gid INTEGER
 * }
 */

    ber = ber_alloc_t( LBER_USE_DER );
    if (ber == NULL) {
        return LDAP_OPERATIONS_ERROR;
    }

    switch (res->response_type) {
        case RESP_SID:
            ret = ber_printf(ber,"{es}", res->response_type, res->data.sid);
            break;
        case RESP_NAME:
            ret = ber_printf(ber,"{e{ss}}", res->response_type,
                                            res->data.name.domain_name,
                                            res->data.name.object_name);
            break;
        case RESP_USER:
            ret = ber_printf(ber,"{e{ssii}}", res->response_type,
                                              res->data.user.domain_name,
                                              res->data.user.user_name,
                                              res->data.user.uid,
                                              res->data.user.gid);
            break;
        case RESP_GROUP:
            ret = ber_printf(ber,"{e{ssi}}", res->response_type,
                                             res->data.group.domain_name,
                                             res->data.group.group_name,
                                             res->data.group.gid);
            break;
        default:
            ber_free(ber, 1);
            return LDAP_OPERATIONS_ERROR;
    }

    if (ret == -1) {
        ber_free(ber, 1);
        return LDAP_OPERATIONS_ERROR;
    }

    ret = ber_flatten(ber, ret_val);
    if (ret == -1) {
        ber_free(ber, 1);
        return LDAP_OPERATIONS_ERROR;
    }

    ber_free(ber, 1);

    return LDAP_SUCCESS;
}
