/* -*- 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 <k5-int.h>
#include <k5-hex.h>
#include "kdb5_ldap_util.h"
#include "kdb5_ldap_list.h"

/* Get the configured LDAP service password file.  The caller should free the
 * result with profile_release_string(). */
static krb5_error_code
get_conf_service_file(profile_t profile, const char *realm, char **path_out)
{
    char *subsection, *path;
    long ret;

    *path_out = NULL;

    /* Get the [dbmodules] subsection for realm. */
    ret = profile_get_string(profile, KDB_REALM_SECTION, realm,
                             KDB_MODULE_POINTER, realm, &subsection);
    if (ret)
        return ret;

    /* Look up the password file in the [dbmodules] subsection. */
    ret = profile_get_string(profile, KDB_MODULE_SECTION, subsection,
                             KRB5_CONF_LDAP_SERVICE_PASSWORD_FILE, NULL,
                             &path);
    profile_release_string(subsection);
    if (ret)
        return ret;

    if (path == NULL) {
        /* Look up the password file in [dbdefaults] as a fallback. */
        ret = profile_get_string(profile, KDB_MODULE_DEF_SECTION,
                                 KRB5_CONF_LDAP_SERVICE_PASSWORD_FILE, NULL,
                                 NULL, &path);
        if (ret)
            return ret;
    }

    if (path == NULL) {
        k5_setmsg(util_context, ENOENT,
                  _("ldap_service_password_file not configured"));
        return ENOENT;
    }

    *path_out = path;
    return 0;
}

/*
 * 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, *hexpasswd = NULL;
    char line[MAX_LEN];
    FILE *pfile = NULL;
    krb5_boolean print_usage = FALSE;
    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;
        }
    } else { /* argc == 2 */
        service_object = strdup (argv[1]);
        if (service_object == NULL) {
            com_err(me, ENOMEM, _("while setting service object password"));
            goto cleanup;
        }

        ret = get_conf_service_file(util_context->profile,
                                    util_context->default_realm, &file_name);
        if (ret) {
            com_err(me, ret, _("while getting service password filename"));
            goto cleanup;
        }
    }

    /* 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 */
    ret = k5_hex_encode(passwd, passwd_len, FALSE, &hexpasswd);
    zap(passwd, passwd_len);
    if (ret != 0) {
        com_err(me, ret, _("Failed to convert the password to hexadecimal"));
        goto cleanup;
    }

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

    /* set password in the file */
    old_mode = umask(0177);
    pfile = WRITABLEFOPEN(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) < 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;
#ifdef USE_SELINUX
        void *selabel;
#endif

        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);
#ifdef USE_SELINUX
        selabel = krb5int_push_fscreatecon_for(file_name);
#endif
        newfile = fopen(tmp_file, "w");
#ifdef USE_SELINUX
        krb5int_pop_fscreatecon(selabel);
#endif
        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) < 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 password 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:

    zapfreestr(hexpasswd);

    if (service_object)
        free(service_object);

    profile_release_string(file_name);

    if (tmp_file)
        free(tmp_file);

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

    if (ret)
        exit_status++;
}
