/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* plugins/kdb/ldap/ldap_util/kdb5_ldap_services.c */
/* Copyright (c) 2004-2005, Novell, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *   * The copyright holder's name is not used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Create / Delete / Modify / View / List service objects.
 */

/*
 * Service objects have rights over realm objects and principals. The following
 * functions manage the service objects.
 */

#include <stdio.h>
#include <k5-int.h>
#include "kdb5_ldap_util.h"
#include "kdb5_ldap_list.h"

#ifdef HAVE_EDIRECTORY

static krb5_error_code
convert_realm_name2dn_list(char **list, const char *krbcontainer_loc);

static krb5_error_code
rem_service_entry_from_file(int argc,
                            char *argv[],
                            char *file_name,
                            char *service_object);

static void
print_service_params(krb5_ldap_service_params *lserparams, int mask);

extern char *yes;
extern krb5_boolean db_inited;

static int
process_host_list(char **host_list, int servicetype)
{
    krb5_error_code retval = 0;
    char *pchr = NULL;
    char host_str[MAX_LEN_LIST_ENTRY] = "", proto_str[PROTOCOL_STR_LEN + 1] = "", port_str[PORT_STR_LEN + 1] = "";
    int j = 0;

    /* Protocol and port number processing */
    for (j = 0; host_list[j]; j++) {
        /* Look for one hash */
        if ((pchr = strchr(host_list[j], HOST_INFO_DELIMITER))) {
            unsigned int hostname_len = pchr - host_list[j];

            /* Check input for buffer overflow */
            if (hostname_len >= MAX_LEN_LIST_ENTRY) {
                retval = EINVAL;
                goto cleanup;
            }

            /* First copy off the host name portion */
            strncpy (host_str, host_list[j], hostname_len);

            /* Parse for the protocol string and translate to number */
            strncpy (proto_str, pchr + 1, PROTOCOL_STR_LEN);
            if (!strcmp(proto_str, "udp"))
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_NUM_UDP);
            else if (!strcmp(proto_str, "tcp"))
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_NUM_TCP);
            else
                proto_str[0] = '\0'; /* Make the string null if invalid */

            /* Look for one more hash */
            if ((pchr = strchr(pchr + 1, HOST_INFO_DELIMITER))) {
                /* Parse for the port string and check if it is numeric */
                strncpy (port_str, pchr + 1, PORT_STR_LEN);
                if (!strtol(port_str, NULL, 10)) /* Not a valid number */
                    port_str[0] = '\0';
            } else
                port_str[0] = '\0';
        } else { /* We have only host name */
            strncpy (host_str, host_list[j], MAX_LEN_LIST_ENTRY - 1);
            proto_str[0] = '\0';
            port_str[0] = '\0';
        }

        /* Now, based on service type, fill in suitable protocol
           and port values if they are absent or not matching */
        if (servicetype == LDAP_KDC_SERVICE) {
            if (proto_str[0] == '\0')
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_DEFAULT_KDC);

            if (port_str[0] == '\0')
                snprintf (port_str, sizeof(port_str), "%d", PORT_DEFAULT_KDC);
        } else if (servicetype == LDAP_ADMIN_SERVICE) {
            if (proto_str[0] == '\0')
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_DEFAULT_ADM);
            else if (strcmp(proto_str, "1")) {
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_DEFAULT_ADM);

                /* Print warning message */
                printf ("Admin Server supports only TCP protocol, hence setting that\n");
            }

            if (port_str[0] == '\0')
                snprintf (port_str, sizeof(port_str), "%d", PORT_DEFAULT_ADM);
        } else if (servicetype == LDAP_PASSWD_SERVICE) {
            if (proto_str[0] == '\0')
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_DEFAULT_PWD);
            else if (strcmp(proto_str, "0")) {
                snprintf (proto_str, sizeof(proto_str), "%d",
                          PROTOCOL_DEFAULT_PWD);

                /* Print warning message */
                printf ("Password Server supports only UDP protocol, hence setting that\n");
            }

            if (port_str[0] == '\0')
                sprintf (port_str, "%d", PORT_DEFAULT_PWD);
        }

        /* Finally form back the string */
        free (host_list[j]);
        host_list[j] = (char*) malloc(sizeof(char) *
                                      (strlen(host_str) + strlen(proto_str) + strlen(port_str) + 2 + 1));
        if (host_list[j] == NULL) {
            retval = ENOMEM;
            goto cleanup;
        }
        snprintf (host_list[j], strlen(host_str) + strlen(proto_str) + strlen(port_str) + 2 + 1,
                  "%s#%s#%s", host_str, proto_str, port_str);
    }

cleanup:
    return retval;
}


/*
 * Given a realm name, this function will convert it to a DN by appending the
 * Kerberos container location.
 */
static krb5_error_code
convert_realm_name2dn_list(char **list, const char *krbcontainer_loc)
{
    krb5_error_code retval = 0;
    char temp_str[MAX_DN_CHARS] = "\0";
    char *temp_node = NULL;
    int i = 0;

    if (list == NULL) {
        return EINVAL;
    }

    for (i = 0; (list[i] != NULL) && (i < MAX_LIST_ENTRIES); i++) {
        /* Restrict copying to max. length to avoid buffer overflow */
        snprintf (temp_str, MAX_DN_CHARS, "cn=%s,%s", list[i], krbcontainer_loc);

        /* Make copy of string to temporary node */
        temp_node = strdup(temp_str);
        if (list[i] == NULL) {
            retval = ENOMEM;
            goto cleanup;
        }

        /* On success, free list node and attach new one */
        free (list[i]);
        list[i] = temp_node;
        temp_node = NULL;
    }

cleanup:
    return retval;
}


/*
 * This function will create a service object on the LDAP Server, with the
 * specified attributes.
 */
void
kdb5_ldap_create_service(int argc, char *argv[])
{
    char *me = progname;
    krb5_error_code retval = 0;
    krb5_ldap_service_params *srvparams = NULL;
    krb5_boolean print_usage = FALSE;
    krb5_boolean no_msg = FALSE;
    int mask = 0;
    char **extra_argv = NULL;
    int extra_argc = 0;
    int i = 0;
    krb5_ldap_realm_params *rparams = NULL;
    int rmask = 0;
    int rightsmask =0;
    char **temprdns = NULL;
    char *realmName = NULL;
    kdb5_dal_handle *dal_handle = NULL;
    krb5_ldap_context *ldap_context=NULL;
    krb5_boolean service_obj_created = FALSE;

    /* Check for number of arguments */
    if ((argc < 3) || (argc > 10)) {
        exit_status++;
        goto err_usage;
    }

    /* Allocate memory for service parameters structure */
    srvparams = (krb5_ldap_service_params*) calloc(1, sizeof(krb5_ldap_service_params));
    if (srvparams == NULL) {
        retval = ENOMEM;
        goto cleanup;
    }

    dal_handle = util_context->dal_handle;
    ldap_context = (krb5_ldap_context *) dal_handle->db_context;

    /* Allocate memory for extra arguments to be used for setting
       password -- it's OK to allocate as much as the total number
       of arguments */
    extra_argv = (char **) calloc((unsigned int)argc, sizeof(char*));
    if (extra_argv == NULL) {
        retval = ENOMEM;
        goto cleanup;
    }

    /* Set first of the extra arguments as the program name */
    extra_argv[0] = me;
    extra_argc++;

    /* Read Kerberos container info, to construct realm DN from name
     * and for assigning rights
     */
    if ((retval = krb5_ldap_read_krbcontainer_params(util_context,
                                                     &(ldap_context->krbcontainer)))) {
        com_err(me, retval, "while reading Kerberos container information");
        goto cleanup;
    }

    /* Parse all arguments */
    for (i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "-kdc")) {
            srvparams->servicetype = LDAP_KDC_SERVICE;
        } else if (!strcmp(argv[i], "-admin")) {
            srvparams->servicetype = LDAP_ADMIN_SERVICE;
        } else if (!strcmp(argv[i], "-pwd")) {
            srvparams->servicetype = LDAP_PASSWD_SERVICE;
        } else if (!strcmp(argv[i], "-servicehost")) {
            if (++i > argc - 1)
                goto err_usage;

            srvparams->krbhostservers = (char **)calloc(MAX_LIST_ENTRIES,
                                                        sizeof(char *));
            if (srvparams->krbhostservers == NULL) {
                retval = ENOMEM;
                goto cleanup;
            }

            if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER,
                                          srvparams->krbhostservers))) {
                goto cleanup;
            }

            if ((retval = process_host_list (srvparams->krbhostservers,
                                             srvparams->servicetype))) {
                goto cleanup;
            }

            mask |= LDAP_SERVICE_HOSTSERVER;
        } else if (!strcmp(argv[i], "-realm")) {
            if (++i > argc - 1)
                goto err_usage;

            srvparams->krbrealmreferences = (char **)calloc(MAX_LIST_ENTRIES,
                                                            sizeof(char *));
            if (srvparams->krbrealmreferences == NULL) {
                retval = ENOMEM;
                goto cleanup;
            }

            if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER,
                                          srvparams->krbrealmreferences))) {
                goto cleanup;
            }

            /* Convert realm names to realm DNs */
            if ((retval = convert_realm_name2dn_list(
                     srvparams->krbrealmreferences,
                     ldap_context->krbcontainer->DN))) {
                goto cleanup;
            }

            mask |= LDAP_SERVICE_REALMREFERENCE;
        }
        /* If argument is none of the above and beginning with '-',
         * it must be related to password -- collect it
         * to pass onto kdb5_ldap_set_service_password()
         */
        else if (*(argv[i]) == '-') {
            /* Checking for options of setting the password for the
             * service (by using 'setsrvpw') is not modular. --need to
             * have a common function that can be shared with 'setsrvpw'
             */
            if (!strcmp(argv[i], "-randpw")) {
                extra_argv[extra_argc] = argv[i];
                extra_argc++;
            } else if (!strcmp(argv[i], "-fileonly")) {
                extra_argv[extra_argc] = argv[i];
                extra_argc++;
            }
            /* For '-f' option alone, pick up the following argument too */
            else if (!strcmp(argv[i], "-f")) {
                extra_argv[extra_argc] = argv[i];
                extra_argc++;

                if (++i > argc - 1)
                    goto err_usage;

                extra_argv[extra_argc] = argv[i];
                extra_argc++;
            } else { /* Any other option is invalid */
                exit_status++;
                goto err_usage;
            }
        } else { /* Any other argument must be service DN */
            /* First check if service DN is already provided --
             * if so, there's a usage error
             */
            if (srvparams->servicedn != NULL) {
                com_err(me, EINVAL, "while creating service object");
                goto err_usage;
            }

            /* If not present already, fill up service DN */
            srvparams->servicedn = strdup(argv[i]);
            if (srvparams->servicedn == NULL) {
                com_err(me, ENOMEM, "while creating service object");
                goto err_nomsg;
            }
        }
    }

    /* No point in proceeding further if service DN value is not available */
    if (srvparams->servicedn == NULL) {
        com_err(me, EINVAL, "while creating service object");
        goto err_usage;
    }

    if (srvparams->servicetype == 0) { /* Not provided and hence not set */
        com_err(me, EINVAL, "while creating service object");
        goto err_usage;
    }

    /* Create object with all attributes provided */
    if ((retval = krb5_ldap_create_service(util_context, srvparams, mask)))
        goto cleanup;

    service_obj_created = TRUE;

    /* ** NOTE ** srvparams structure should not be modified, as it is
     * used for deletion of the service object in case of any failures
     * from now on.
     */

    /* Set password too */
    if (extra_argc >= 1) {
        /* Set service DN as the last argument */
        extra_argv[extra_argc] = strdup(srvparams->servicedn);
        if (extra_argv[extra_argc] == NULL) {
            retval = ENOMEM;
            goto cleanup;
        }
        extra_argc++;

        if ((retval = kdb5_ldap_set_service_password(extra_argc, extra_argv)) != 0) {
            goto err_nomsg;
        }
    }
    /* Rights assignment */
    if (mask & LDAP_SERVICE_REALMREFERENCE) {

        printf("%s","Changing rights for the service object. Please wait ... ");
        fflush(stdout);

        rightsmask =0;
        rightsmask |= LDAP_REALM_RIGHTS;
        rightsmask |= LDAP_SUBTREE_RIGHTS;

        if ((srvparams != NULL) && (srvparams->krbrealmreferences != NULL)) {
            for (i=0; (srvparams->krbrealmreferences[i] != NULL); i++) {

                /* Get the realm name, not the dn */
                temprdns = ldap_explode_dn(srvparams->krbrealmreferences[i], 1);

                if (temprdns[0] == NULL) {
                    retval = EINVAL;
                    goto cleanup;
                }

                realmName = strdup(temprdns[0]);
                if (realmName == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_ldap_read_realm_params(util_context,
                                                          realmName, &rparams, &rmask))) {
                    com_err(me, retval, "while reading information of realm '%s'",
                            realmName);
                    goto cleanup;
                }

                if ((retval = krb5_ldap_add_service_rights(util_context,
                                                           srvparams->servicetype, srvparams->servicedn,
                                                           realmName, rparams->subtree, rparams->containerref, rightsmask))) {
                    printf("failed\n");
                    com_err(me, retval, "while assigning rights '%s'",
                            srvparams->servicedn);
                    goto cleanup;
                }

                if (rparams)
                    krb5_ldap_free_realm_params(rparams);
            }
        }
        printf("done\n");
    }
    goto cleanup;

err_usage:
    print_usage = TRUE;

err_nomsg:
    no_msg = TRUE;

cleanup:

    if ((retval != 0) && (service_obj_created == TRUE)) {
        /* This is for deleting the service object if something goes
         * wrong in creating the service object
         */

        /* srvparams is populated from the user input and should be correct as
         * we were successful in creating a service object. Reusing the same
         */
        krb5_ldap_delete_service(util_context, srvparams, srvparams->servicedn);
    }

    /* Clean-up structure */
    krb5_ldap_free_service (util_context, srvparams);

    if (extra_argv) {
        free (extra_argv);
        extra_argv = NULL;
    }
    if (realmName) {
        free(realmName);
        realmName = NULL;
    }
    if (print_usage)
        db_usage (CREATE_SERVICE);

    if (retval) {
        if (!no_msg)
            com_err(me, retval, "while creating service object");

        exit_status++;
    }

    return;
}


/*
 * This function will modify the attributes of a given service
 * object on the LDAP Server
 */
void
kdb5_ldap_modify_service(int argc, char *argv[])
{
    char *me = progname;
    krb5_error_code retval = 0;
    krb5_ldap_service_params *srvparams = NULL;
    krb5_boolean print_usage = FALSE;
    krb5_boolean no_msg = FALSE;
    char *servicedn = NULL;
    int i = 0;
    int in_mask = 0, out_mask = 0;
    int srvhost_flag = 0, realmdn_flag = 0;
    char **list = NULL;
    int existing_entries = 0, new_entries = 0;
    char **temp_ptr = NULL;
    krb5_ldap_realm_params *rparams = NULL;
    int j = 0;
    int rmask = 0;
    int rightsmask =0;
    char **oldrealmrefs = NULL;
    char **newrealmrefs = NULL;
    char **temprdns = NULL;
    char *realmName = NULL;
    kdb5_dal_handle *dal_handle = NULL;
    krb5_ldap_context *ldap_context=NULL;

    /* Check for number of arguments */
    if ((argc < 3) || (argc > 10)) {
        exit_status++;
        goto err_usage;
    }

    dal_handle = util_context->dal_handle;
    ldap_context = (krb5_ldap_context *) dal_handle->db_context;

    /* Parse all arguments, only to pick up service DN (Pass 1) */
    for (i = 1; i < argc; i++) {
        /* Skip arguments next to 'servicehost'
           and 'realmdn' arguments */
        if (!strcmp(argv[i], "-servicehost")) {
            ++i;
        } else if (!strcmp(argv[i], "-clearservicehost")) {
            ++i;
        } else if (!strcmp(argv[i], "-addservicehost")) {
            ++i;
        } else if (!strcmp(argv[i], "-realm")) {
            ++i;
        } else if (!strcmp(argv[i], "-clearrealm")) {
            ++i;
        } else if (!strcmp(argv[i], "-addrealm")) {
            ++i;
        } else { /* Any other argument must be service DN */
            /* First check if service DN is already provided --
               if so, there's a usage error */
            if (servicedn != NULL) {
                com_err(me, EINVAL, "while modifying service object");
                goto err_usage;
            }

            /* If not present already, fill up service DN */
            servicedn = strdup(argv[i]);
            if (servicedn == NULL) {
                com_err(me, ENOMEM, "while modifying service object");
                goto err_nomsg;
            }
        }
    }

    /* No point in proceeding further if service DN value is not available */
    if (servicedn == NULL) {
        com_err(me, EINVAL, "while modifying service object");
        goto err_usage;
    }

    retval = krb5_ldap_read_service(util_context, servicedn, &srvparams, &in_mask);
    if (retval) {
        com_err(me, retval, "while reading information of service '%s'",
                servicedn);
        goto err_nomsg;
    }

    /* Read Kerberos container info, to construct realm DN from name
     * and for assigning rights
     */
    if ((retval = krb5_ldap_read_krbcontainer_params(util_context,
                                                     &(ldap_context->krbcontainer)))) {
        com_err(me, retval, "while reading Kerberos container information");
        goto cleanup;
    }

    /* Parse all arguments, but skip the service DN (Pass 2) */
    for (i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "-servicehost")) {
            if (++i > argc - 1)
                goto err_usage;

            /* Free the old list if available */
            if (srvparams->krbhostservers) {
                krb5_free_list_entries (srvparams->krbhostservers);
                free (srvparams->krbhostservers);
            }

            srvparams->krbhostservers = (char **)calloc(MAX_LIST_ENTRIES,
                                                        sizeof(char *));
            if (srvparams->krbhostservers == NULL) {
                retval = ENOMEM;
                goto cleanup;
            }

            if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER,
                                          srvparams->krbhostservers))) {
                goto cleanup;
            }

            if ((retval = process_host_list (srvparams->krbhostservers,
                                             srvparams->servicetype))) {
                goto cleanup;
            }

            out_mask |= LDAP_SERVICE_HOSTSERVER;

            /* Set flag to ignore 'add' and 'clear' */
            srvhost_flag = 1;
        } else if (!strcmp(argv[i], "-clearservicehost")) {
            if (++i > argc - 1)
                goto err_usage;

            if (!srvhost_flag) {
                /* If attribute doesn't exist, don't permit 'clear' option */
                if ((in_mask & LDAP_SERVICE_HOSTSERVER) == 0) {
                    /* Send out some proper error message here */
                    com_err(me, EINVAL, "service host list is empty\n");
                    goto err_nomsg;
                }

                /* Allocate list for processing */
                list = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                if (list == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER, list)))
                    goto cleanup;

                if ((retval = process_host_list (list, srvparams->servicetype))) {
                    goto cleanup;
                }

                list_modify_str_array(&(srvparams->krbhostservers),
                                      (const char**)list, LIST_MODE_DELETE);

                out_mask |= LDAP_SERVICE_HOSTSERVER;

                /* Clean up */
                free (list);
                list = NULL;
            }
        } else if (!strcmp(argv[i], "-addservicehost")) {
            if (++i > argc - 1)
                goto err_usage;

            if (!srvhost_flag) {
                /* Allocate list for processing */
                list = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                if (list == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER, list)))
                    goto cleanup;

                if ((retval = process_host_list (list, srvparams->servicetype))) {
                    goto cleanup;
                }

                /* Call list_modify_str_array() only if host server attribute
                 * exists already --Actually, it's better to handle this
                 * within list_modify_str_array()
                 */
                if (in_mask & LDAP_SERVICE_HOSTSERVER) {
                    /* Re-size existing list */
                    existing_entries = list_count_str_array(srvparams->krbhostservers);
                    new_entries = list_count_str_array(list);
                    temp_ptr = (char **) realloc(srvparams->krbhostservers,
                                                 sizeof(char *) * (existing_entries + new_entries + 1));
                    if (temp_ptr == NULL) {
                        retval = ENOMEM;
                        goto cleanup;
                    }
                    srvparams->krbhostservers = temp_ptr;

                    list_modify_str_array(&(srvparams->krbhostservers),
                                          (const char**)list, LIST_MODE_ADD);

                    /* Clean up */
                    free (list);
                    list = NULL;
                } else
                    srvparams->krbhostservers = list;

                out_mask |= LDAP_SERVICE_HOSTSERVER;
            }
        } else if (!strcmp(argv[i], "-realm")) {
            if (++i > argc - 1)
                goto err_usage;

            if ((in_mask & LDAP_SERVICE_REALMREFERENCE) && (srvparams->krbrealmreferences)) {
                if (!oldrealmrefs) {
                    /* Store the old realm list for removing rights */
                    oldrealmrefs = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                    if (oldrealmrefs == NULL) {
                        retval = ENOMEM;
                        goto cleanup;
                    }

                    for (j = 0; srvparams->krbrealmreferences[j] != NULL; j++) {
                        oldrealmrefs[j] = strdup(srvparams->krbrealmreferences[j]);
                        if (oldrealmrefs[j] == NULL) {
                            retval = ENOMEM;
                            goto cleanup;
                        }
                    }
                    oldrealmrefs[j] = NULL;
                }

                /* Free the old list if available */
                krb5_free_list_entries (srvparams->krbrealmreferences);
                free (srvparams->krbrealmreferences);
            }

            srvparams->krbrealmreferences = (char **)calloc(MAX_LIST_ENTRIES,
                                                            sizeof(char *));
            if (srvparams->krbrealmreferences == NULL) {
                retval = ENOMEM;
                goto cleanup;
            }

            if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER,
                                          srvparams->krbrealmreferences))) {
                goto cleanup;
            }

            /* Convert realm names to realm DNs */
            if ((retval = convert_realm_name2dn_list(
                     srvparams->krbrealmreferences,
                     ldap_context->krbcontainer->DN))) {
                goto cleanup;
            }

            out_mask |= LDAP_SERVICE_REALMREFERENCE;

            /* Set flag to ignore 'add' and 'clear' */
            realmdn_flag = 1;
        } else if (!strcmp(argv[i], "-clearrealm")) {
            if (++i > argc - 1)
                goto err_usage;

            if (!realmdn_flag) {
                /* If attribute doesn't exist, don't permit 'clear' option */
                if (((in_mask & LDAP_SERVICE_REALMREFERENCE) == 0) || (srvparams->krbrealmreferences == NULL)) {
                    /* Send out some proper error message here */
                    goto err_nomsg;
                }

                if (!oldrealmrefs) {
                    /* Store the old realm list for removing rights */
                    oldrealmrefs = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                    if (oldrealmrefs == NULL) {
                        retval = ENOMEM;
                        goto cleanup;
                    }

                    for (j = 0; srvparams->krbrealmreferences[j] != NULL; j++) {
                        oldrealmrefs[j] = strdup(srvparams->krbrealmreferences[j]);
                        if (oldrealmrefs[j] == NULL) {
                            retval = ENOMEM;
                            goto cleanup;
                        }
                    }
                    oldrealmrefs[j] = NULL;
                }

                /* Allocate list for processing */
                list = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                if (list == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER, list)))
                    goto cleanup;

                /* Convert realm names to realm DNs */
                if ((retval = convert_realm_name2dn_list(list,
                                                         ldap_context->krbcontainer->DN))) {
                    goto cleanup;
                }

                list_modify_str_array(&(srvparams->krbrealmreferences),
                                      (const char**)list, LIST_MODE_DELETE);

                out_mask |= LDAP_SERVICE_REALMREFERENCE;

                /* Clean up */
                free (list);
                list = NULL;
            }
        } else if (!strcmp(argv[i], "-addrealm")) {
            if (++i > argc - 1)
                goto err_usage;

            if (!realmdn_flag) {
                /* Allocate list for processing */
                list = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                if (list == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_parse_list(argv[i], LIST_DELIMITER, list)))
                    goto cleanup;

                /* Convert realm names to realm DNs */
                if ((retval = convert_realm_name2dn_list(list,
                                                         ldap_context->krbcontainer->DN))) {
                    goto cleanup;
                }

                if ((in_mask & LDAP_SERVICE_REALMREFERENCE) && (srvparams->krbrealmreferences) && (!oldrealmrefs)) {
                    /* Store the old realm list for removing rights */
                    oldrealmrefs = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
                    if (oldrealmrefs == NULL) {
                        retval = ENOMEM;
                        goto cleanup;
                    }

                    for (j = 0; srvparams->krbrealmreferences[j] != NULL; j++) {
                        oldrealmrefs[j] = strdup(srvparams->krbrealmreferences[j]);
                        if (oldrealmrefs[j] == NULL) {
                            retval = ENOMEM;
                            goto cleanup;
                        }
                    }
                    oldrealmrefs[j] = NULL;
                }

                /* Call list_modify_str_array() only if realm DN attribute
                 * exists already -- Actually, it's better to handle this
                 * within list_modify_str_array() */
                if (in_mask & LDAP_SERVICE_REALMREFERENCE) {
                    /* Re-size existing list */
                    existing_entries = list_count_str_array(
                        srvparams->krbrealmreferences);
                    new_entries = list_count_str_array(list);
                    temp_ptr = (char **) realloc(srvparams->krbrealmreferences,
                                                 sizeof(char *) * (existing_entries + new_entries + 1));
                    if (temp_ptr == NULL) {
                        retval = ENOMEM;
                        goto cleanup;
                    }
                    srvparams->krbrealmreferences = temp_ptr;

                    list_modify_str_array(&(srvparams->krbrealmreferences),
                                          (const char**)list, LIST_MODE_ADD);

                    /* Clean up */
                    free (list);
                    list = NULL;
                } else
                    srvparams->krbrealmreferences = list;

                out_mask |= LDAP_SERVICE_REALMREFERENCE;
            }
        } else {
            /* Any other argument must be service DN
               -- skip it */
        }
    }

    /* Modify attributes of object */
    if ((retval = krb5_ldap_modify_service(util_context, srvparams, out_mask)))
        goto cleanup;

    /* Service rights modification code */
    if (out_mask & LDAP_SERVICE_REALMREFERENCE) {

        printf("%s","Changing rights for the service object. Please wait ... ");
        fflush(stdout);

        newrealmrefs = (char**) calloc(MAX_LIST_ENTRIES, sizeof(char*));
        if (newrealmrefs == NULL) {
            retval = ENOMEM;
            goto cleanup;
        }

        if ((srvparams != NULL) && (srvparams->krbrealmreferences != NULL)) {
            for (j = 0; srvparams->krbrealmreferences[j] != NULL; j++) {
                newrealmrefs[j] = strdup(srvparams->krbrealmreferences[j]);
                if (newrealmrefs[j] == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }
            }
            newrealmrefs[j] = NULL;
        }
        disjoint_members(oldrealmrefs, newrealmrefs);

        /* Delete the rights for the given service, on each of the realm
         * container & subtree in the old realm reference list.
         */
        if (oldrealmrefs) {
            rightsmask = 0;
            rightsmask |= LDAP_REALM_RIGHTS;
            rightsmask |= LDAP_SUBTREE_RIGHTS;

            for (i = 0; (oldrealmrefs[i] != NULL); i++) {
                /* Get the realm name, not the dn */
                temprdns = ldap_explode_dn(oldrealmrefs[i], 1);

                if (temprdns[0] == NULL) {
                    retval = EINVAL;
                    goto cleanup;
                }

                realmName = strdup(temprdns[0]);
                if (realmName == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_ldap_read_realm_params(util_context,
                                                          realmName, &rparams, &rmask))) {
                    com_err(me, retval, "while reading information of realm '%s'",
                            realmName);
                    goto err_nomsg;
                }

                if ((retval = krb5_ldap_delete_service_rights(util_context,
                                                              srvparams->servicetype, srvparams->servicedn,
                                                              realmName, rparams->subtree, rparams->containerref, rightsmask))) {
                    printf("failed\n");
                    com_err(me, retval, "while assigning rights '%s'",
                            srvparams->servicedn);
                    goto err_nomsg;
                }

                if (rparams)
                    krb5_ldap_free_realm_params(rparams);
            }
        }

        /* Add the rights for the given service, on each of the realm
         * container & subtree in the new realm reference list.
         */
        if (newrealmrefs) {
            rightsmask = 0;
            rightsmask |= LDAP_REALM_RIGHTS;
            rightsmask |= LDAP_SUBTREE_RIGHTS;

            for (i = 0; (newrealmrefs[i] != NULL); i++) {
                /* Get the realm name, not the dn */
                temprdns = ldap_explode_dn(newrealmrefs[i], 1);

                if (temprdns[0] == NULL) {
                    retval = EINVAL;
                    goto cleanup;
                }

                realmName = strdup(temprdns[0]);
                if (realmName == NULL) {
                    retval = ENOMEM;
                    goto cleanup;
                }

                if ((retval = krb5_ldap_read_krbcontainer_params(util_context,
                                                                 &(ldap_context->krbcontainer)))) {
                    com_err(me, retval,
                            "while reading Kerberos container information");
                    goto cleanup;
                }

                if ((retval = krb5_ldap_read_realm_params(util_context,
                                                          realmName, &rparams, &rmask))) {
                    com_err(me, retval, "while reading information of realm '%s'",
                            realmName);
                    goto err_nomsg;
                }

                if ((retval = krb5_ldap_add_service_rights(util_context,
                                                           srvparams->servicetype, srvparams->servicedn,
                                                           realmName, rparams->subtree, rparams->containerref, rightsmask))) {
                    printf("failed\n");
                    com_err(me, retval, "while assigning rights '%s'",
                            srvparams->servicedn);
                    goto err_nomsg;
                }

                if (rparams) {
                    krb5_ldap_free_realm_params(rparams);
                    rparams = NULL;
                }
            }
            printf("done\n");
        }
    }
    goto cleanup;

err_usage:
    print_usage = TRUE;

err_nomsg:
    no_msg = TRUE;

cleanup:
    /* Clean-up structure */
    krb5_ldap_free_service(util_context, srvparams);

    if (servicedn)
        free(servicedn);

    if (list) {
        free(list);
        list = NULL;
    }

    if (oldrealmrefs) {
        for (i = 0; oldrealmrefs[i] != NULL; i++)
            free(oldrealmrefs[i]);
        free(oldrealmrefs);
    }

    if (newrealmrefs) {
        for (i = 0; newrealmrefs[i] != NULL; i++)
            free(newrealmrefs[i]);
        free(newrealmrefs);
    }
    if (realmName) {
        free(realmName);
        realmName = NULL;
    }

    if (print_usage)
        db_usage(MODIFY_SERVICE);

    if (retval) {
        if (!no_msg)
            com_err(me, retval, "while modifying service object");
        exit_status++;
    }

    return;
}


/*
 * This function will delete the entry corresponding to the service object
 * from the service password file.
 */
static krb5_error_code
rem_service_entry_from_file(int argc, char *argv[], char *file_name,
                            char *service_object)
{
    int     st        = EINVAL;
    char    *me       = progname;
    char    *tmp_file = NULL;
    int     tmpfd     = -1;
    FILE    *pfile    = NULL;
    unsigned int len  = 0;
    char    line[MAX_LEN]={0};
    mode_t  omask     = umask(077);

    /* Check for permissions on the password file */
    if (access(file_name, W_OK) == -1) {
        /* If the specified file itself is not there, no need to show error */
        if (errno == ENOENT) {
            st=0;
            goto cleanup;
        } else {
            com_err(me, errno, "while deleting entry from file %s", file_name);
            goto cleanup;
        }
    }

    /* Create a temporary file which contains all the entries except the
       entry for the given service dn */
    pfile = fopen(file_name, "r+");
    if (pfile == NULL) {
        com_err(me, errno, "while deleting entry from file %s", file_name);
        goto cleanup;
    }
    set_cloexec_file(pfile);

    /* Create a new file with the extension .tmp */
    tmp_file = (char *)malloc(strlen(file_name) + 4 + 1);
    if (tmp_file == NULL) {
        com_err(me, ENOMEM, "while deleting entry from file");
        fclose(pfile);
        goto cleanup;
    }
    snprintf (tmp_file, strlen(file_name) + 4 + 1, "%s%s", file_name, ".tmp");


    tmpfd = creat(tmp_file, S_IRUSR|S_IWUSR);
    umask(omask);
    if (tmpfd == -1) {
        com_err(me, errno, "while deleting entry from file\n");
        fclose(pfile);
        goto cleanup;
    }

    /* Copy only those lines which donot have the specified service dn */
    while (fgets(line, MAX_LEN, pfile) != NULL) {
        if ((strstr(line, service_object) != NULL) &&
            (line[strlen(service_object)] == '#')) {
            continue;
        } else {
            len = strlen(line);
            if (write(tmpfd, line, len) != len) {
                com_err(me, errno, "while deleting entry from file\n");
                close(tmpfd);
                unlink(tmp_file);
                fclose(pfile);
                goto cleanup;
            }
        }
    }

    fclose(pfile);
    if (unlink(file_name) == 0) {
        link(tmp_file, file_name);
    } else {
        com_err(me, errno, "while deleting entry from file\n");
    }
    unlink(tmp_file);

    st=0;

cleanup:

    if (tmp_file)
        free(tmp_file);

    return st;
}


/*
 * This function will delete the service object from the LDAP Server
 * and unlink the references to the Realm objects (if any)
 */
void
kdb5_ldap_destroy_service(int argc, char *argv[])
{
    int i = 0;
    char buf[5] = {0};
    krb5_error_code retval = EINVAL;
    int force = 0;
    char *servicedn = NULL;
    char *stashfilename = NULL;
    int mask = 0;
    krb5_ldap_service_params *lserparams = NULL;
    krb5_boolean print_usage = FALSE;

    if ((argc < 2) || (argc > 5)) {
        exit_status++;
        goto err_usage;
    }

    for (i=1; i < argc; i++) {

        if (strcmp(argv[i],"-force")==0) {
            force++;
        } else if (strcmp(argv[i],"-f")==0) {
            if (argv[i+1]) {
                stashfilename=strdup(argv[i+1]);
                if (stashfilename == NULL) {
                    com_err(progname, ENOMEM, "while destroying service");
                    exit_status++;
                    goto cleanup;
                }
                i++;
            } else {
                exit_status++;
                goto err_usage;
            }
        } else {
            if ((argv[i]) && (servicedn == NULL)) {
                servicedn=strdup(argv[i]);
                if (servicedn == NULL) {
                    com_err(progname, ENOMEM, "while destroying service");
                    exit_status++;
                    goto cleanup;
                }
            } else {
                exit_status++;
                goto err_usage;
            }
        }
    }

    if (!servicedn) {
        exit_status++;
        goto err_usage;
    }

    if (!force) {
        printf("This will delete the service object '%s', are you sure?\n", servicedn);
        printf("(type 'yes' to confirm)? ");
        if (fgets(buf, sizeof(buf), stdin) == NULL) {
            exit_status++;
            goto cleanup;;
        }
        if (strcmp(buf, yes)) {
            exit_status++;
            goto cleanup;
        }
    }

    if ((retval = krb5_ldap_read_service(util_context, servicedn,
                                         &lserparams, &mask))) {
        com_err(progname, retval, "while destroying service '%s'",servicedn);
        exit_status++;
        goto cleanup;
    }

    retval = krb5_ldap_delete_service(util_context, lserparams, servicedn);

    if (retval) {
        com_err(progname, retval, "while destroying service '%s'", servicedn);
        exit_status++;
        goto cleanup;
    }

    if (stashfilename == NULL) {
        stashfilename = strdup(DEF_SERVICE_PASSWD_FILE);
        if (stashfilename == NULL) {
            com_err(progname, ENOMEM, "while destroying service");
            exit_status++;
            goto cleanup;
        }
    }
    printf("** service object '%s' deleted.\n", servicedn);
    retval = rem_service_entry_from_file(argc, argv, stashfilename, servicedn);

    if (retval)
        printf("** error removing service object entry '%s' from password file.\n",
               servicedn);

    goto cleanup;


err_usage:
    print_usage = TRUE;

cleanup:

    if (lserparams) {
        krb5_ldap_free_service(util_context, lserparams);
    }

    if (servicedn) {
        free(servicedn);
    }

    if (stashfilename) {
        free(stashfilename);
    }

    if (print_usage) {
        db_usage(DESTROY_SERVICE);
    }

    return;
}


/*
 * This function will display information about the given service object
 */
void
kdb5_ldap_view_service(int argc, char *argv[])
{
    krb5_ldap_service_params *lserparams = NULL;
    krb5_error_code retval = 0;
    char *servicedn = NULL;
    int mask = 0;
    krb5_boolean print_usage = FALSE;

    if (!(argc == 2)) {
        exit_status++;
        goto err_usage;
    }

    servicedn=strdup(argv[1]);
    if (servicedn == NULL) {
        com_err(progname, ENOMEM, "while viewing service");
        exit_status++;
        goto cleanup;
    }

    if ((retval = krb5_ldap_read_service(util_context, servicedn, &lserparams, &mask))) {
        com_err(progname, retval, "while viewing service '%s'",servicedn);
        exit_status++;
        goto cleanup;
    }

    print_service_params(lserparams, mask);

    goto cleanup;

err_usage:
    print_usage = TRUE;

cleanup:

    if (lserparams) {
        krb5_ldap_free_service(util_context, lserparams);
    }

    if (servicedn)
        free(servicedn);

    if (print_usage) {
        db_usage(VIEW_SERVICE);
    }

    return;
}


/*
 * This function will list the DNs of kerberos services present on
 * the LDAP Server under a specific sub-tree (entire tree by default)
 */
void
kdb5_ldap_list_services(int argc, char *argv[])
{
    char *me = progname;
    krb5_error_code retval = 0;
    char *basedn = NULL;
    char **list = NULL;
    char **plist = NULL;
    krb5_boolean print_usage = FALSE;

    /* Check for number of arguments */
    if ((argc != 1) && (argc != 3)) {
        exit_status++;
        goto err_usage;
    }

    /* Parse base DN argument if present */
    if (argc == 3) {
        if (strcmp(argv[1], "-basedn")) {
            retval = EINVAL;
            goto err_usage;
        }

        basedn = strdup(argv[2]);
        if (basedn == NULL) {
            com_err(me, ENOMEM, "while listing services");
            exit_status++;
            goto cleanup;
        }
    }

    retval = krb5_ldap_list_services(util_context, basedn, &list);
    if ((retval != 0) || (list == NULL)) {
        exit_status++;
        goto cleanup;
    }

    for (plist = list; *plist != NULL; plist++) {
        printf("%s\n", *plist);
    }

    goto cleanup;

err_usage:
    print_usage = TRUE;

cleanup:
    if (list != NULL) {
        krb5_free_list_entries (list);
        free (list);
    }

    if (basedn)
        free (basedn);

    if (print_usage) {
        db_usage(LIST_SERVICE);
    }

    if (retval) {
        com_err(me, retval, "while listing policy objects");
        exit_status++;
    }

    return;
}


/*
 * This function will print the service object information
 * to the standard output
 */
static void
print_service_params(krb5_ldap_service_params *lserparams, int mask)
{
    int            i=0;

    /* Print the service dn */
    printf("%20s%-20s\n","Service dn: ",lserparams->servicedn);

    /* Print the service type of the object to be read */
    if (lserparams->servicetype == LDAP_KDC_SERVICE) {
        printf("%20s%-20s\n","Service type: ","kdc");
    } else if (lserparams->servicetype == LDAP_ADMIN_SERVICE) {
        printf("%20s%-20s\n","Service type: ","admin");
    } else if (lserparams->servicetype == LDAP_PASSWD_SERVICE) {
        printf("%20s%-20s\n","Service type: ","pwd");
    }

    /* Print the host server values */
    printf("%20s\n","Service host list: ");
    if (mask & LDAP_SERVICE_HOSTSERVER) {
        for (i=0; lserparams->krbhostservers[i] != NULL; ++i) {
            printf("%20s%-50s\n","",lserparams->krbhostservers[i]);
        }
    }

    /* Print the realm reference dn values */
    printf("%20s\n","Realm DN list: ");
    if (mask & LDAP_SERVICE_REALMREFERENCE) {
        for (i=0; lserparams && lserparams->krbrealmreferences && lserparams->krbrealmreferences[i] != NULL; ++i) {
            printf("%20s%-50s\n","",lserparams->krbrealmreferences[i]);
        }
    }

    return;
}


/*
 * This function will generate random  password of length(RANDOM_PASSWD_LEN)
 *
 *
 * INPUT:
 *      ctxt - context
 *
 * OUTPUT:
 *     RANDOM_PASSWD_LEN length random password
 */
static int
generate_random_password(krb5_context ctxt, char **randpwd,
                         unsigned int *passlen)
{
    char *random_pwd = NULL;
    int ret = 0;
    krb5_data data;
    int i=0;
    /*int len = 0;*/

    /* setting random password length in the range 16-32 */
    srand((unsigned int)(time(0) ^ getpid()));

    data.length = RANDOM_PASSWD_LEN;
    random_pwd = (char *)malloc(data.length + 1);
    if (random_pwd == NULL) {
        com_err("setsrvpw", ENOMEM, "while generating random password");
        return ENOMEM;
    }
    memset(random_pwd, 0, data.length + 1);
    data.data = random_pwd;

    ret = krb5_c_random_make_octets(ctxt, &data);
    if (ret) {
        com_err("setsrvpw", ret, "Error generating random password");
        free(random_pwd);
        return ret;
    }

    for (i=0; i<data.length; i++) {
        /* restricting to ascii chars. Need to change this when 8.8 supports */
        if ((unsigned char)random_pwd[i] > 127) {
            random_pwd[i] = (unsigned char)random_pwd[i] % 128;
        } else if (random_pwd[i] == 0) {
            random_pwd[i] = (rand()/(RAND_MAX/127 + 1))+1;
        }
    }

    *randpwd = random_pwd;
    *passlen = data.length;

    return 0;
}


/*
 * This function will set the password of the service object in the directory
 * and/or the specified service password file.
 *
 *
 * INPUT:
 *      argc - contains the number of arguments for this sub-command
 *      argv - array of arguments for this sub-command
 *
 * OUTPUT:
 *      void
 */
int
kdb5_ldap_set_service_password(int argc, char **argv)
{
    krb5_ldap_context *lparams = NULL;
    char *file_name = NULL;
    char *tmp_file = NULL;
    char *me = progname;
    int filelen = 0;
    int random_passwd = 0;
    int set_dir_pwd = 1;
    krb5_boolean db_init_local = FALSE;
    char *service_object = NULL;
    char *passwd = NULL;
    char *prompt1 = NULL;
    char *prompt2 = NULL;
    unsigned int passwd_len = 0;
    krb5_error_code errcode = -1;
    int retval = 0, i = 0;
    krb5_boolean print_usage = FALSE;
    FILE *pfile = NULL;
    char *str = NULL;
    char line[MAX_LEN];
    kdb5_dal_handle *dal_handle = NULL;
    struct data encrypted_passwd = {0, NULL};

    /* The arguments for setsrv password should contain the service object DN
     * and options to specify whether the password should be updated in file only
     * or both file and directory. So the possible combination of arguments are:
     * setsrvpw servicedn                               wherein argc is 2
     * setsrvpw -fileonly servicedn                     wherein argc is 3
     * setsrvpw -randpw servicedn                       wherein argc is 3
     * setsrvpw -f filename servicedn                   wherein argc is 4
     * setsrvpw -fileonly -f filename servicedn         wherein argc is 5
     * setsrvpw -randpw -f filename servicedn           wherein argc is 5
     */
    if ((argc < 2) || (argc > 5)) {
        print_usage = TRUE;
        goto cleanup;
    }

    dal_handle = util_context->dal_handle;
    lparams = (krb5_ldap_context *) dal_handle->db_context;

    if (lparams == NULL) {
        printf("%s: Invalid LDAP handle\n", me);
        goto cleanup;
    }

    /* Parse the arguments */
    for (i = 1; i < argc -1 ; i++) {
        if (strcmp(argv[i], "-randpw") == 0) {
            random_passwd = 1;
        } else if (strcmp(argv[i], "-fileonly") == 0) {
            set_dir_pwd = 0;
        } else if (strcmp(argv[i], "-f") == 0) {
            if (argv[++i] == NULL) {
                print_usage = TRUE;
                goto cleanup;
            }

            file_name = strdup(argv[i]);
            if (file_name == NULL) {
                com_err(me, ENOMEM, "while setting service object password");
                goto cleanup;
            }
            /* Verify if the file location has the proper file name
             * for eg, if the file location is a directory like /home/temp/,
             * we reject it.
             */
            filelen = strlen(file_name);
            if ((filelen == 0) || (file_name[filelen-1] == '/')) {
                printf("%s: Filename not specified for setting service object password\n", me);
                print_usage = TRUE;
                goto cleanup;
            }
        } else {
            printf("%s: Invalid option specified for \"setsrvpw\" command\n", me);
            print_usage = TRUE;
            goto cleanup;
        }
    }

    if (i != argc-1) {
        print_usage = TRUE;
        goto cleanup;
    }

    service_object = strdup(argv[i]);
    if (service_object == NULL) {
        com_err(me, ENOMEM, "while setting service object password");
        goto cleanup;
    }

    if (strlen(service_object) == 0) {
        printf("%s: Service object not specified for \"setsrvpw\" command\n", me);
        print_usage = TRUE;
        goto cleanup;
    }

    if (service_object[0] == '-') {
        print_usage = TRUE;
        goto cleanup;
    }

    if (file_name == NULL) {
        file_name = strdup(DEF_SERVICE_PASSWD_FILE);
        if (file_name == NULL) {
            com_err(me, ENOMEM, "while setting service object password");
            goto cleanup;
        }
    }

    if (set_dir_pwd) {
        if (db_inited == FALSE) {
            if ((errcode = krb5_ldap_db_init(util_context, lparams))) {
                com_err(me, errcode, "while initializing database");
                goto cleanup;
            }
            db_init_local = TRUE;
        }
    }

    if (random_passwd) {
        if (!set_dir_pwd) {
            printf("%s: Invalid option specified for \"setsrvpw\" command\n", me);
            print_usage = TRUE;
            goto cleanup;
        } else {
            /* Generate random password */

            if ((errcode = generate_random_password(util_context, &passwd, &passwd_len))) {
                printf("%s: Failed to set service object password\n", me);
                goto cleanup;
            }
            passwd_len = strlen(passwd);
        }
    } else {
        /* Get the service object password from the terminal */
        passwd = (char *)malloc(MAX_SERVICE_PASSWD_LEN + 1);
        if (passwd == NULL) {
            com_err(me, ENOMEM, "while setting service object password");
            goto cleanup;
        }
        memset(passwd, 0, MAX_SERVICE_PASSWD_LEN + 1);
        passwd_len = MAX_SERVICE_PASSWD_LEN;

        if (asprintf(&prompt1, "Password for \"%s\"", service_object) < 0) {
            com_err(me, ENOMEM, "while setting service object password");
            goto cleanup;
        }

        if (asprintf(&prompt2, "Re-enter password for \"%s\"",
                     service_object) < 0) {
            com_err(me, ENOMEM, "while setting service object password");
            free(prompt1);
            goto cleanup;
        }

        retval = krb5_read_password(util_context, prompt1, prompt2, passwd, &passwd_len);
        free(prompt1);
        free(prompt2);
        if (retval) {
            com_err(me, retval, "while setting service object password");
            memset(passwd, 0, MAX_SERVICE_PASSWD_LEN);
            goto cleanup;
        }
        if (passwd_len == 0) {
            printf("%s: Invalid password\n", me);
            memset(passwd, 0, MAX_SERVICE_PASSWD_LEN);
            goto cleanup;
        }
        passwd_len = strlen(passwd);
    }

    /* Hex the password */
    {
        krb5_data pwd, hex;
        pwd.length = passwd_len;
        pwd.data = passwd;

        errcode = tohex(pwd, &hex);
        if (errcode != 0) {
            if (hex.length != 0) {
                memset(hex.data, 0, hex.length);
                free(hex.data);
            }
            com_err(me, errcode, "Failed to convert the password to hex");
            memset(passwd, 0, passwd_len);
            goto cleanup;
        }
        /* Password = {HEX}<encrypted password>:<encrypted key> */
        if (asprintf(&str, "%s#{HEX}%s\n", service_object, hex.data) < 0) {
            com_err(me, ENOMEM, "while setting service object password");
            memset(passwd, 0, passwd_len);
            memset(hex.data, 0, hex.length);
            free(hex.data);
            goto cleanup;
        }
        encrypted_passwd.value = (unsigned char *)str;
        encrypted_passwd.len = strlen(str);
        memset(hex.data, 0, hex.length);
        free(hex.data);
    }

    /* We should check if the file exists and we have permission to write into that file */
    if (access(file_name, W_OK) == -1) {
        if (errno == ENOENT) {
            mode_t omask;
            int fd = -1;

            printf("File does not exist. Creating the file %s...\n", file_name);
            omask = umask(077);
            fd = creat(file_name, S_IRUSR|S_IWUSR);
            umask(omask);
            if (fd == -1) {
                com_err(me, errno, "Error creating file %s", file_name);
                memset(passwd, 0, passwd_len);
                goto cleanup;
            }
            close(fd);
        } else {
            com_err(me, errno, "Unable to access the file %s", file_name);
            memset(passwd, 0, passwd_len);
            goto cleanup;
        }
    }

    if (set_dir_pwd) {
        if ((errcode = krb5_ldap_set_service_passwd(util_context, service_object, passwd)) != 0) {
            com_err(me, errcode, "Failed to set password for service object %s", service_object);
            memset(passwd, 0, passwd_len);
            goto cleanup;
        }
    }

    memset(passwd, 0, passwd_len);


    /* TODO: file lock for the service password file */
    /* set password in the file */
    pfile = fopen(file_name, "r+");
    if (pfile == NULL) {
        com_err(me, errno, "Failed to open file %s", file_name);
        goto cleanup;
    }
    set_cloexec_file(pfile);

    while (fgets(line, MAX_LEN, pfile) != NULL) {
        if ((str = strstr(line, service_object)) != NULL) {
            if (line[strlen(service_object)] == '#') {
                break;
            }
            str = NULL;
        }
    }
    if (str == NULL) {
        if (feof(pfile)) {
            /* If the service object dn is not present in the service password file */
            if (fwrite(encrypted_passwd.value, (unsigned int)encrypted_passwd.len, 1, pfile) != 1) {
                com_err(me, errno, "Failed to write service object password to file");
                goto cleanup;
            }
        } else {
            com_err(me, errno, "Error reading service object password file");
            goto cleanup;
        }
        fclose(pfile);
        pfile = NULL;
    } else {
        /* Password entry for the service object is already present in the file */
        /* Delete the existing entry and add the new entry */
        FILE *newfile = NULL;
        mode_t omask;

        /* Create a new file with the extension .tmp */
        if (asprintf(&tmp_file,"%s.tmp",file_name) < 0) {
            com_err(me, ENOMEM, "while setting service object password");
            goto cleanup;
        }

        omask = umask(077);
        newfile = fopen(tmp_file, "w+");
        umask(omask);
        if (newfile == NULL) {
            com_err(me, errno, "Error creating file %s", tmp_file);
            goto cleanup;
        }
        set_cloexec_file(newfile);

        fseek(pfile, 0, SEEK_SET);
        while (fgets(line, MAX_LEN, pfile) != NULL) {
            if (((str = strstr(line, service_object)) != NULL) && (line[strlen(service_object)] == '#')) {
                if (fprintf(newfile, "%s", encrypted_passwd.value) < 0) {
                    com_err(me, errno, "Failed to write service object password to file");
                    fclose(newfile);
                    unlink(tmp_file);
                    goto cleanup;
                }
            } else {
                if (fprintf(newfile, "%s", line) < 0) {
                    com_err(me, errno, "Failed to write service object password to file");
                    fclose(newfile);
                    unlink(tmp_file);
                    goto cleanup;
                }
            }
        }

        if (!feof(pfile)) {
            com_err(me, errno, "Error reading service object password file");
            fclose(newfile);
            unlink(tmp_file);
            goto cleanup;
        }

        /* TODO: file lock for the service password file */
        fclose(pfile);
        pfile = NULL;

        fclose(newfile);
        newfile = NULL;

        if (unlink(file_name) == 0) {
            link(tmp_file, file_name);
        } else {
            com_err(me, errno, "Failed to write service object password to file");
            unlink(tmp_file);
            goto cleanup;
        }
        unlink(tmp_file);
    }
    errcode = 0;

cleanup:
    if (db_init_local)
        krb5_ldap_close(util_context);

    if (service_object)
        free(service_object);

    if (file_name)
        free(file_name);

    if (passwd)
        free(passwd);

    if (encrypted_passwd.value) {
        memset(encrypted_passwd.value, 0, encrypted_passwd.len);
        free(encrypted_passwd.value);
    }

    if (pfile)
        fclose(pfile);

    if (tmp_file)
        free(tmp_file);

    if (print_usage)
        db_usage(SET_SRV_PW);

    return errcode;
}

#else /* #ifdef HAVE_EDIRECTORY */

/*
 * Convert the user supplied password into hexadecimal and stash it. Only a
 * little more secure than storing plain password in the file ...
 */
void
kdb5_ldap_stash_service_password(int argc, char **argv)
{
    int ret = 0;
    unsigned int passwd_len = 0;
    char *me = progname;
    char *service_object = NULL;
    char *file_name = NULL, *tmp_file = NULL;
    char passwd[MAX_SERVICE_PASSWD_LEN];
    char *str = NULL;
    char line[MAX_LEN];
    FILE *pfile = NULL;
    krb5_boolean print_usage = FALSE;
    krb5_data hexpasswd = {0, 0, NULL};
    mode_t old_mode = 0;

    /*
     * Format:
     *   stashsrvpw [-f filename] service_dn
     * where
     *   'service_dn' is the DN of the service object
     *   'filename' is the path of the stash file
     */
    if (argc != 2 && argc != 4) {
        print_usage = TRUE;
        goto cleanup;
    }

    if (argc == 4) {
        /* Find the stash file name */
        if (strcmp (argv[1], "-f") == 0) {
            if (((file_name = strdup (argv[2])) == NULL) ||
                ((service_object = strdup (argv[3])) == NULL)) {
                com_err(me, ENOMEM,
                        _("while setting service object password"));
                goto cleanup;
            }
        } else if (strcmp (argv[2], "-f") == 0) {
            if (((file_name = strdup (argv[3])) == NULL) ||
                ((service_object = strdup (argv[1])) == NULL)) {
                com_err(me, ENOMEM,
                        _("while setting service object password"));
                goto cleanup;
            }
        } else {
            print_usage = TRUE;
            goto cleanup;
        }
        if (file_name == NULL) {
            com_err(me, ENOMEM, _("while setting service object password"));
            goto cleanup;
        }
    } else { /* argc == 2 */
        char *section;

        service_object = strdup (argv[1]);
        if (service_object == NULL) {
            com_err(me, ENOMEM, _("while setting service object password"));
            goto cleanup;
        }

        /* Pick up the stash-file name from krb5.conf */
        profile_get_string(util_context->profile, KDB_REALM_SECTION,
                           util_context->default_realm, KDB_MODULE_POINTER, NULL, &section);

        if (section == NULL) {
            profile_get_string(util_context->profile, KDB_MODULE_DEF_SECTION,
                               KDB_MODULE_POINTER, NULL, NULL, &section);
            if (section == NULL) {
                /* Stash file path neither in krb5.conf nor on command line */
                file_name = strdup(DEF_SERVICE_PASSWD_FILE);
                if (file_name == NULL) {
                    com_err(me, ENOMEM,
                            _("while setting service object password"));
                    goto cleanup;
                }
                goto done;
            }
        }

        profile_get_string (util_context->profile, KDB_MODULE_SECTION, section,
                            "ldap_service_password_file", NULL, &file_name);
    }
done:

    /* Get password from user */
    {
        char prompt1[256], prompt2[256];

        /* Get the service object password from the terminal */
        memset(passwd, 0, sizeof (passwd));
        passwd_len = sizeof (passwd);

        snprintf(prompt1, sizeof(prompt1), _("Password for \"%s\""),
                 service_object);

        snprintf(prompt2, sizeof(prompt2), _("Re-enter password for \"%s\""),
                 service_object);

        ret = krb5_read_password(util_context, prompt1, prompt2, passwd, &passwd_len);
        if (ret != 0) {
            com_err(me, ret, _("while setting service object password"));
            memset(passwd, 0, sizeof (passwd));
            goto cleanup;
        }

        if (passwd_len == 0) {
            printf(_("%s: Invalid password\n"), me);
            memset(passwd, 0, MAX_SERVICE_PASSWD_LEN);
            goto cleanup;
        }
    }

    /* Convert the password to hexadecimal */
    {
        krb5_data pwd;

        pwd.length = passwd_len;
        pwd.data = passwd;

        ret = tohex(pwd, &hexpasswd);
        if (ret != 0) {
            com_err(me, ret,
                    _("Failed to convert the password to hexadecimal"));
            memset(passwd, 0, passwd_len);
            goto cleanup;
        }
    }
    memset(passwd, 0, passwd_len);

    /* TODO: file lock for the service password file */

    /* set password in the file */
    old_mode = umask(0177);
    pfile = fopen(file_name, "a+");
    if (pfile == NULL) {
        com_err(me, errno, _("Failed to open file %s: %s"), file_name,
                strerror (errno));
        goto cleanup;
    }
    set_cloexec_file(pfile);
    rewind (pfile);
    umask(old_mode);

    while (fgets (line, MAX_LEN, pfile) != NULL) {
        if ((str = strstr (line, service_object)) != NULL) {
            /* White spaces not allowed */
            if (line [strlen (service_object)] == '#')
                break;
            str = NULL;
        }
    }

    if (str == NULL) {
        if (feof(pfile)) {
            /* If the service object dn is not present in the service password file */
            if (fprintf(pfile, "%s#{HEX}%s\n", service_object, hexpasswd.data) < 0) {
                com_err(me, errno,
                        _("Failed to write service object password to file"));
                fclose(pfile);
                goto cleanup;
            }
        } else {
            com_err(me, errno,
                    _("Error reading service object password file"));
            fclose(pfile);
            goto cleanup;
        }
        fclose(pfile);
    } else {
        /*
         * Password entry for the service object is already present in the file
         * Delete the existing entry and add the new entry
         */
        FILE *newfile;

        mode_t omask;

        /* Create a new file with the extension .tmp */
        if (asprintf(&tmp_file,"%s.tmp",file_name) < 0) {
            com_err(me, ENOMEM, _("while setting service object password"));
            fclose(pfile);
            goto cleanup;
        }

        omask = umask(077);
        newfile = fopen(tmp_file, "w");
        umask (omask);
        if (newfile == NULL) {
            com_err(me, errno, _("Error creating file %s"), tmp_file);
            fclose(pfile);
            goto cleanup;
        }
        set_cloexec_file(newfile);

        fseek(pfile, 0, SEEK_SET);
        while (fgets(line, MAX_LEN, pfile) != NULL) {
            if (((str = strstr(line, service_object)) != NULL) &&
                (line[strlen(service_object)] == '#')) {
                if (fprintf(newfile, "%s#{HEX}%s\n", service_object, hexpasswd.data) < 0) {
                    com_err(me, errno, _("Failed to write service object "
                                         "password to file"));
                    fclose(newfile);
                    unlink(tmp_file);
                    fclose(pfile);
                    goto cleanup;
                }
            } else {
                if (fprintf (newfile, "%s", line) < 0) {
                    com_err(me, errno, _("Failed to write service object "
                                         "password to file"));
                    fclose(newfile);
                    unlink(tmp_file);
                    fclose(pfile);
                    goto cleanup;
                }
            }
        }

        if (!feof(pfile)) {
            com_err(me, errno,
                    _("Error reading service object password file"));
            fclose(newfile);
            unlink(tmp_file);
            fclose(pfile);
            goto cleanup;
        }

        /* TODO: file lock for the service passowrd file */

        fclose(pfile);
        fclose(newfile);

        ret = rename(tmp_file, file_name);
        if (ret != 0) {
            com_err(me, errno,
                    _("Failed to write service object password to file"));
            goto cleanup;
        }
    }
    ret = 0;

cleanup:

    if (hexpasswd.length != 0) {
        memset(hexpasswd.data, 0, hexpasswd.length);
        free(hexpasswd.data);
    }

    if (service_object)
        free(service_object);

    if (file_name)
        free(file_name);

    if (tmp_file)
        free(tmp_file);

    if (print_usage)
        usage();
/*      db_usage(STASH_SRV_PW); */

    if (ret)
        exit_status++;
}

#endif /* #ifdef HAVE_EDIRECTORY */
