root/usr.sbin/installer/dfuibe_installer/fn_configure.c
/*
 * Copyright (c)2004,2015 The DragonFly Project.  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.
 *
 *   Neither the name of the DragonFly Project nor the names of its
 *   contributors may be 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 HOLDERS 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.
 */

/*
 * fn_configure.c
 * Configuration functions for installer.
 * This includes both Configure the LiveCD Environment, and
 * Configure an Installed System (there is considerable overlap.)
 * $Id: fn_configure.c,v 1.82 2005/03/25 05:24:00 cpressey Exp $
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#ifdef ENABLE_NLS
#include <libintl.h>
#define _(String) gettext (String)
#else
#define _(String) (String)
#endif

#include "libaura/mem.h"
#include "libaura/dict.h"
#include "libaura/fspred.h"

#include "libdfui/dfui.h"
#include "libdfui/system.h"

#include "libinstaller/commands.h"
#include "libinstaller/confed.h"
#include "libinstaller/diskutil.h"
#include "libinstaller/functions.h"
#include "libinstaller/uiutil.h"

#include "fn.h"
#include "flow.h"
#include "pathnames.h"

struct config_vars      *rc_conf;
struct config_vars      *resolv_conf;

/** CONFIGURE FUNCTIONS **/

#define GECOS_NOT_ALLOWED       ":,\\\""
#define FILENAME_NOT_ALLOWED    ":;`~!#$^&*()={}[]\\|?<>'\" "
#define MEMBERSHIPS_NOT_ALLOWED ":;`~!@#$%^&*()+={}[]\\|/?<>'\" "
#define USERNAME_NOT_ALLOWED    (MEMBERSHIPS_NOT_ALLOWED ",")

void
fn_add_user(struct i_fn_args *a)
{
        struct dfui_dataset *ds, *new_ds;
        struct dfui_form *f;
        struct dfui_response *r;
        struct commands *cmds;
        struct command *cmd;
        const char *username, *home, *passwd_1, *passwd_2, *gecos;
        const char *shell, *uid, *group, *groups;
        const char *passwd_env = "passwd";
        int done = 0;

        f = dfui_form_create(
            "add_user",
            _("Add user"),
            _("Here you can add a user to an installed system.\n\n"
            "You can leave the Home Directory, User ID, and Login Group "
            "fields empty if you want these items to be automatically "
            "allocated by the system."),
            "",
            "f", "username", _("Username"),
            _("Enter the username the user will log in as"), "",
            "f", "gecos", _("Real Name"),
            _("Enter the real name (or GECOS field) of this user"), "",
            "f", "passwd_1", _("Password"),
            _("Enter the user's password (will not be displayed)"), "",
            "p", "obscured", "true",
            "f", "passwd_2", _("Password (Again)"),
            _("Re-enter the user's password to confirm"), "",
            "p", "obscured", "true",
            "f", "shell", _("Shell"),
            _("Enter the full path to the user's shell program"), "",
            "f", "home", _("Home Directory"),
            _("Enter the full path to the user's home directory, or leave blank"), "",
            "f", "uid", _("User ID"),
            _("Enter this account's numeric user id, or leave blank"), "",
            "f", "group", _("Login Group"),
            _("Enter the primary group for this account, or leave blank"), "",
            "f", "groups", _("Other Group Memberships"),
            _("Enter a comma-separated list of other groups "
            "that this user should belong to"), "",
            "a", "ok", _("Accept and Add"), "", "",
            "a", "cancel", _("Return to Configure Menu"), "", "",
            "p", "accelerator", "ESC",
            NULL
        );

        ds = dfui_dataset_new();
        dfui_dataset_celldata_add(ds, "username", "");
        dfui_dataset_celldata_add(ds, "gecos", "");
        dfui_dataset_celldata_add(ds, "passwd_1", "");
        dfui_dataset_celldata_add(ds, "passwd_2", "");
        dfui_dataset_celldata_add(ds, "shell", "/bin/tcsh");
        dfui_dataset_celldata_add(ds, "home", "");
        dfui_dataset_celldata_add(ds, "uid", "");
        dfui_dataset_celldata_add(ds, "group", "");
        dfui_dataset_celldata_add(ds, "groups", "");
        dfui_form_dataset_add(f, ds);

        while (!done) {
                if (!dfui_be_present(a->c, f, &r))
                        abort_backend();

                if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) {
                        done = 1;
                        dfui_response_free(r);
                        break;
                }

                new_ds = dfui_dataset_dup(dfui_response_dataset_get_first(r));
                dfui_form_datasets_free(f);
                dfui_form_dataset_add(f, new_ds);

                /* Fetch form field values. */

                username = dfui_dataset_get_value(new_ds, "username");
                home = dfui_dataset_get_value(new_ds, "home");
                gecos = dfui_dataset_get_value(new_ds, "gecos");
                shell = dfui_dataset_get_value(new_ds, "shell");
                passwd_1 = dfui_dataset_get_value(new_ds, "passwd_1");
                passwd_2 = dfui_dataset_get_value(new_ds, "passwd_2");
                uid = dfui_dataset_get_value(new_ds, "uid");
                group = dfui_dataset_get_value(new_ds, "group");
                groups = dfui_dataset_get_value(new_ds, "groups");

                if (strlen(username) == 0) {
                        inform(a->c, _("You must enter a username."));
                        done = 0;
                } else if (strcmp(passwd_1, passwd_2) != 0) {
                        inform(a->c, _("The passwords do not match."));
                        done = 0;
                } else if (!assert_clean(a->c, _("Username"), username, USERNAME_NOT_ALLOWED) ||
                    !assert_clean(a->c, _("Real Name"), gecos, GECOS_NOT_ALLOWED) ||
                    !assert_clean(a->c, _("Shell"), shell, FILENAME_NOT_ALLOWED) ||
                    !assert_clean(a->c, _("Home Directory"), home, FILENAME_NOT_ALLOWED) ||
                    !assert_clean(a->c, _("User ID"), uid, USERNAME_NOT_ALLOWED) ||
                    !assert_clean(a->c, _("Login Group"), group, USERNAME_NOT_ALLOWED) ||
                    !assert_clean(a->c, _("Group Memberships"), groups, MEMBERSHIPS_NOT_ALLOWED)) {
                        done = 0;
                } else if (setenv(passwd_env, passwd_1, 1) != 0) {
                        inform(a->c, _("setenv() failed."));
                        done = 0;
                } else if (!is_program("%s%s", a->os_root, shell) &&
                    strcmp(shell, "/nonexistent") != 0) {
                        inform(a->c, _("Chosen shell does not exist on the system."));
                        done = 0;
                } else {
                        cmds = commands_new();

                        command_add(cmds, "%s%s %smnt/ /%s useradd "
                            "'%s' %s%s %s%s -c \"%s\" %s%s -s %s %s%s %s",
                            a->os_root, cmd_name(a, "CHROOT"),
                            a->os_root, cmd_name(a, "PW"),
                            username,
                            strlen(uid) == 0 ? "" : "-u ", uid,
                            strlen(group) == 0 ? "" : "-g ", group,
                            gecos,
                            strlen(home) == 0 ? "" : "-d ", home,
                            shell,
                            strlen(groups) == 0 ? "" : "-G ", groups,
                            (strlen(home) == 0 || !is_dir("%s", home)) ?
                            "-m -k /usr/share/skel" : "");

                        cmd = command_add(cmds, "%s%s \"$%s\" | "
                            "%s%s %smnt/ /%s usermod '%s' -h 0",
                            a->os_root, cmd_name(a, "ECHO"),
                            passwd_env,
                            a->os_root, cmd_name(a, "CHROOT"),
                            a->os_root, cmd_name(a, "PW"),
                            username);
                        command_set_desc(cmd, _("Setting password..."));

                        if (commands_execute(a, cmds)) {
                                inform(a->c, _("User `%s' was added."), username);
                                done = 1;
                        } else {
                                inform(a->c, _("User was not successfully added."));
                                done = 0;
                        }

                        unsetenv(passwd_env);
                        commands_free(cmds);
                }

                dfui_response_free(r);
        }

        dfui_form_free(f);
}

void
fn_root_passwd(struct i_fn_args *a)
{
        struct dfui_dataset *ds, *new_ds;
        struct dfui_form *f;
        struct dfui_response *r;
        struct commands *cmds;
        struct command *cmd;
        const char *root_passwd_1, *root_passwd_2;
        const char *passwd_env = "passwd";
        int done = 0;

        f = dfui_form_create(
            "root_passwd",
            _("Set Root Password"),
            _("Here you can set the super-user (root) password."),
            "",

            "f", "root_passwd_1", _("Root password"),
            _("Enter the root password you would like to use"), "",
            "p", "obscured", "true",
            "f", "root_passwd_2", _("Root password again"),
            _("Enter the root password again to confirm"), "",
            "p", "obscured", "true",

            "a", "ok", _("Accept and Set Password"), "", "",
            "a", "cancel", _("Return to Configure Menu"), "", "",
            "p", "accelerator", "ESC",

            NULL
        );

        ds = dfui_dataset_new();
        dfui_dataset_celldata_add(ds, "root_passwd_1", "");
        dfui_dataset_celldata_add(ds, "root_passwd_2", "");
        dfui_form_dataset_add(f, ds);

        while (!done) {
                if (!dfui_be_present(a->c, f, &r))
                        abort_backend();

                if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) {
                        done = 1;
                        dfui_response_free(r);
                        break;
                }

                new_ds = dfui_dataset_dup(dfui_response_dataset_get_first(r));
                dfui_form_datasets_free(f);
                dfui_form_dataset_add(f, new_ds);

                root_passwd_1 = dfui_dataset_get_value(new_ds, "root_passwd_1");
                root_passwd_2 = dfui_dataset_get_value(new_ds, "root_passwd_2");

                if (strlen(root_passwd_1) == 0) {
                        inform(a->c, _("You must enter a password."));
                        done = 0;
                } else if (strcmp(root_passwd_1, root_passwd_2) != 0) {
                        inform(a->c, _("The passwords do not match."));
                        done = 0;
                } else if (setenv(passwd_env, root_passwd_1, 1) != 0) {
                        inform(a->c, _("setenv() failed."));
                        done = 0;
                } else {
                        cmds = commands_new();
                        cmd = command_add(cmds, "%s%s \"$%s\" | "
                            "%s%s %smnt/ /%s usermod root -h 0",
                            a->os_root, cmd_name(a, "ECHO"),
                            passwd_env,
                            a->os_root, cmd_name(a, "CHROOT"),
                            a->os_root, cmd_name(a, "PW"));
                        command_set_desc(cmd, _("Setting password..."));
                        if (commands_execute(a, cmds)) {
                                inform(a->c, _("The root password has been changed."));
                                done = 1;
                        } else {
                                inform(a->c, _("An error occurred when "
                                    "setting the root password."));
                                done = 0;
                        }
                        unsetenv(passwd_env);
                        commands_free(cmds);
                }

                dfui_response_free(r);
        }

        dfui_form_free(f);
}

void
fn_get_passphrase(struct i_fn_args *a, int confirm)
{
        struct dfui_dataset *ds, *new_ds;
        struct dfui_form *f;
        struct dfui_response *r;
        const char *passphrase_1, *passphrase_2;
        int fd;
        int done = 0;

        if (confirm) {
                f = dfui_form_create(
                    "crypt_passphrase",
                    _("Set Passphrase for Encryption"),
                    _("Please specify the passphrase to be used for the encrypted "
                        "filesystems.\n\n"
                        "Please note that in the LiveCD environment the keymap is "
                        "set to \"US ISO\". "
                        "If you prefer a different keymap for entering the passphrase "
                        "here, you will need to set it manually using kbdcontrol(1)."),
                    "",

                    "f", "passphrase_1", _("Passphrase"),
                    _("Enter the passphrase you would like to use for encryption"), "",
                    "p", "obscured", "true",
                    "f", "passphrase_2", _("Passphrase again"),
                    _("Enter the passphrase again to confirm"), "",
                    "p", "obscured", "true",

                    "a", "ok", _("Accept and Set Passphrase"), "", "",
                    "p", "accelerator", "ESC",

                    NULL
                );
        } else {
                f = dfui_form_create(
                    "crypt_passphrase",
                    _("Specify Decryption Passphrase"),
                    _("Please enter the passphrase to decrypt and mount the "
                        "filesystems.\n\n"
                        "Please note that in the LiveCD environment the keymap is "
                        "set to \"US ISO\". "
                        "If you prefer a different keymap for entering the passphrase "
                        "here, you will need to set it manually using kbdcontrol(1)."),
                    "",

                    "f", "passphrase_1", _("Passphrase"),
                    _("Enter the passphrase"), "",
                    "p", "obscured", "true",

                    "a", "ok", _("OK"), "", "",
                    "p", "accelerator", "ESC",

                    NULL
                );
        }

        ds = dfui_dataset_new();
        dfui_dataset_celldata_add(ds, "passphrase_1", "");
        if (confirm)
                dfui_dataset_celldata_add(ds, "passphrase_2", "");
        dfui_form_dataset_add(f, ds);

        while (!done) {
                if (!dfui_be_present(a->c, f, &r))
                        abort_backend();

                if (strcmp(dfui_response_get_action_id(r), "ok") == 0) {
                        new_ds = dfui_dataset_dup(dfui_response_dataset_get_first(r));
                        dfui_form_datasets_free(f);
                        dfui_form_dataset_add(f, new_ds);

                        /*
                         * Fetch form field values.
                         */

                        passphrase_1 = dfui_dataset_get_value(new_ds, "passphrase_1");
                        passphrase_2 = passphrase_1;
                        if (confirm)
                                passphrase_2 = dfui_dataset_get_value(new_ds, "passphrase_2");

                        if (strlen(passphrase_1) == 0 && strlen(passphrase_2) == 0) {
                                done = 0;
                        } else if (strcmp(passphrase_1, passphrase_2) == 0) {
                                /*
                                 * Passphrases match, write it out.
                                 */
                                fd = open("/tmp/t1", O_RDWR | O_CREAT | O_TRUNC,
                                    S_IRUSR);
                                if (fd != -1) {
                                        write(fd, passphrase_1, strlen(passphrase_1));
                                        close(fd);
                                        done = 1;
                                } else {
                                        inform(a->c, _("write() error"));
                                        done = 0;
                                }
                        } else {
                                /*
                                 * Passphrases don't match, tell the user,
                                 * let them try again.
                                 */
                                inform(a->c, _("The passphrases do not match."));
                                done = 0;
                        }
                }

                dfui_response_free(r);
        }

        dfui_form_free(f);
}

/** LIVECD UTILITIES FUNCTIONS **/

/*
 * String returned by this function must be deallocated by the caller.
 */
char *
fn_select_file(const char *title, const char *desc, const char *help, const char *cancel,
               const char *dir, const char *ext, const struct i_fn_args *a)
{
        DIR *d;
        struct dfui_form *f;
        struct dfui_action *k;
        struct dfui_response *r;
        struct dirent *de;
        char *s;
        struct aura_dict *dict;
        char *rk;
        size_t rk_len;

        f = dfui_form_create(
            "select_file",
            title, desc, help,
            "p", "role", "menu",
            NULL
        );

        dict = aura_dict_new(1, AURA_DICT_SORTED_LIST);
        d = opendir(dir);
        while ((de = readdir(d)) != NULL) {
                if (strcmp(de->d_name, ".") == 0 ||
                    strcmp(de->d_name, "..") == 0 ||
                    strstr(de->d_name, ext) == NULL)
                        continue;
                aura_dict_store(dict, de->d_name, strlen(de->d_name) + 1, "", 1);
        }
        closedir(d);

        aura_dict_rewind(dict);
        while (!aura_dict_eof(dict)) {
                aura_dict_get_current_key(dict, (void **)&rk, &rk_len),
                dfui_form_action_add(f, rk,
                    dfui_info_new(rk, "", ""));
                aura_dict_next(dict);
        }
        aura_dict_free(dict);

        k = dfui_form_action_add(f, "cancel",
            dfui_info_new(cancel, "", ""));
        dfui_action_property_set(k, "accelerator", "ESC");

        if (!dfui_be_present(a->c, f, &r))
                abort_backend();

        s = aura_strdup(dfui_response_get_action_id(r));

        dfui_form_free(f);
        dfui_response_free(r);

        return(s);
}

void
fn_set_kbdmap(struct i_fn_args *a)
{
        struct commands *cmds;
        char *s;
        char filename[256], keymapname[256];

        s = fn_select_file(_("Select Keyboard Map"),
            _("Select a keyboard map appropriate to your keyboard layout."),
            "", _("Return to Utilities Menu"), "/usr/share/syscons/keymaps",
            ".kbd", a);

        if (strcmp(s, "cancel") != 0) {
                cmds = commands_new();
                command_add(cmds, "%s%s -l "
                    "/usr/share/syscons/keymaps/%s < /dev/ttyv0",
                    a->os_root, cmd_name(a, "KBDCONTROL"),
                    s);
                if (commands_execute(a, cmds)) {
                        snprintf(filename, 256, "/usr/share/syscons/keymaps/%s", s);
                        snprintf(keymapname, 256, "%s", filename_noext(basename(filename)));
                        config_var_set(rc_conf, "keymap", keymapname);
                } else {
                        inform(a->c, _("Keyboard map not successfully set."));
                }
                commands_free(cmds);
        }

        free(s);
}

void
fn_set_vidfont(struct i_fn_args *a)
{
        struct commands *cmds;
        char *s;
        char filename[256], variable[256], fontname[256];
        int by = 0;


        s = fn_select_file(_("Select Console Font"),
            _("Select a font appropriate to your video monitor and language."),
            "", _("Return to Utilities Menu"), "/usr/share/syscons/fonts",
            ".fnt", a);

        if (strcmp(s, "cancel") != 0) {
                cmds = commands_new();
                command_add(cmds, "%s%s -f "
                    "/usr/share/syscons/fonts/%s < /dev/ttyv0",
                    a->os_root, cmd_name(a, "VIDCONTROL"),
                    s);
                if (commands_execute(a, cmds)) {
                        if (strstr(s, "8x16") != NULL)
                                by = 16;
                        else if (strstr(s, "8x14") != NULL)
                                by = 14;
                        else
                                by = 8;

                        snprintf(variable, 256, "font8x%d", by);
                        snprintf(filename, 256, "/usr/share/syscons/fonts/%s", s);
                        snprintf(fontname, 256, "%s", filename_noext(basename(filename)));
                        config_var_set(rc_conf, variable, fontname);

                } else {
                        inform(a->c, _("Video font not successfully set."));
                }
                commands_free(cmds);
        }

        free(s);
}

void
fn_set_scrnmap(struct i_fn_args *a)
{
        struct commands *cmds;
        char *s;
        char filename[256], scrnmapname[256];

        s = fn_select_file(_("Select Screen Map"),
            _("Select a mapping for translating characters as they appear "
            "on your video console screen."),
            "", _("Return to Utilities Menu"), "/usr/share/syscons/scrnmaps",
            ".scm", a);

        if (strcmp(s, "cancel") != 0) {
                cmds = commands_new();
                command_add(cmds, "%s%s -l "
                    "/usr/share/syscons/scrnmaps/%s < /dev/ttyv0",
                    a->os_root, cmd_name(a, "VIDCONTROL"),
                    s);
                if (commands_execute(a, cmds)) {
                        snprintf(filename, 256, "/usr/share/syscons/scrnmaps/%s", s);
                        snprintf(scrnmapname, 256, "%s", filename_noext(basename(filename)));
                        config_var_set(rc_conf, "scrnmap", scrnmapname);
                } else {
                        inform(a->c, _("Video font not successfully set."));
                }
                commands_free(cmds);
        }
        free(s);
}

void
fn_set_timezone(struct i_fn_args *a)
{
        struct commands *cmds;
        char *s = NULL;
        char current_path[256], selection[256], temp[256];
        int found_file = 0;
        int result;

        result = dfui_be_present_dialog(a->c, _("Local or UTC (Greenwich Mean Time) clock"),
            _("Yes|No"),
            _("Is this machine's CMOS clock set to UTC?\n\n"
            "If it is set to local time, or you don't know, please choose NO here!"));
        if (result < 1)
                abort_backend();

        cmds = commands_new();
        switch (result) {
                case 1:
                        command_add(cmds, "%s%s -f %s%setc/wall_cmos_clock",
                            a->os_root, cmd_name(a, "RM"),
                            a->os_root, a->cfg_root);
                        break;
                case 2:
                        command_add(cmds, "%s%s %s%setc/wall_cmos_clock",
                            a->os_root, cmd_name(a, "TOUCH"),
                            a->os_root, a->cfg_root);
                        break;
        }
        commands_execute(a, cmds);

        snprintf(current_path, 256, "%s%susr/share/zoneinfo",
            a->os_root, a->cfg_root);
        while (!found_file) {
                if (s != NULL)
                        free(s);
                s = fn_select_file(_("Select Time Zone"),
                    _("Select a Time Zone appropriate to your physical location."),
                    "", _("Return to Utilities Menu"), current_path,
                    "", a);
                if (is_dir("%s/%s", current_path, s)) {
                        snprintf(temp, 256, "%s/%s", current_path, s);
                        strlcpy(current_path, temp, 256);
                } else {
                        if (is_file("%s/%s", current_path, s)) {
                                snprintf(selection, 256, "%s/%s", current_path, s);
                                found_file = 1;
                        }
                        if (strcmp(s, "cancel") == 0) {
                                strlcpy(selection, "cancel", 256);
                                found_file = 1;
                        }
                }
        }
        free(s);

        if (strcmp(selection, "cancel") != 0) {
                command_add(cmds, "%s%s %s %s%setc/localtime",
                    a->os_root, cmd_name(a, "CP"),
                    selection,
                    a->os_root, a->cfg_root);
                if (commands_execute(a, cmds)) {
                        inform(a->c, _("The Time Zone has been set to %s."), selection);
                        setenv("TZ", selection, 1);
                        tzset();
                }
        }
        commands_free(cmds);
}

void
fn_assign_datetime(struct i_fn_args *a)
{
        struct commands *cmds;
        struct dfui_dataset *ds, *new_ds;
        struct dfui_form *f;
        struct dfui_response *r;
        struct tm *tp;
        char temp[256];
        int year, month, dayofmonth, hour, minutes;
        int valid = 1;
        time_t now;

        now = time(NULL);
        tp = localtime(&now);

        f = dfui_form_create(
            "set_datetime",
            _("Set Time/Date"),
            _("Enter the date-time in your timezone."),
            "",

            "f", "year", _("Enter year"),
            _("Enter the current year (e.g. `2004')"), "",
            "f", "month", _("Month"),
            _("Enter the current month (e.g. `07')"), "",
            "f", "dayofmonth", "dayofmonth",
            _("Enter the current day of month (e.g. `30')"), "",
            "f", "hour", "hour",
            _("Enter the current hour (e.g. `07')"), "",
            "f", "minutes", "minutes",
            _("Enter the current minutes (e.g. `59')"), "",

            "a", "ok", _("OK"), "", "",
            "a", "cancel", _("Cancel"), "", "",
            "p", "accelerator", "ESC",

            NULL
        );

        ds = dfui_dataset_new();
        snprintf(temp, 256, "%i", (tp->tm_year+1900));
        dfui_dataset_celldata_add(ds, "year", temp);
        snprintf(temp, 256, "%i", (tp->tm_mon+1));
        dfui_dataset_celldata_add(ds, "month", temp);
        snprintf(temp, 256, "%i", tp->tm_mday);
        dfui_dataset_celldata_add(ds, "dayofmonth", temp);
        snprintf(temp, 256, "%i", tp->tm_hour);
        dfui_dataset_celldata_add(ds, "hour", temp);
        snprintf(temp, 256, "%i", tp->tm_min);
        dfui_dataset_celldata_add(ds, "minutes", temp);
        dfui_form_dataset_add(f, ds);

        if (!dfui_be_present(a->c, f, &r))
                abort_backend();

        if (strcmp(dfui_response_get_action_id(r), "ok") == 0) {
                new_ds = dfui_response_dataset_get_first(r);

                if ((year = atoi(dfui_dataset_get_value(new_ds, "year"))) <= 0)
                        valid = 0;
                month = atoi(dfui_dataset_get_value(new_ds, "month"));
                if (month < 1 || month > 12)
                        valid = 0;
                dayofmonth = atoi(dfui_dataset_get_value(new_ds, "dayofmonth"));
                if (dayofmonth < 1 || dayofmonth > 31)
                        valid = 0;
                hour = atoi(dfui_dataset_get_value(new_ds, "hour"));
                if (hour < 0 || hour > 23)
                        valid = 0;
                minutes = atoi(dfui_dataset_get_value(new_ds, "minutes"));
                if (minutes < 0 || minutes > 59)
                        valid = 0;

                if (valid) {
                        cmds = commands_new();
                        command_add(cmds, "%s%s -n %04d%02d%02d%02d%02d",
                            a->os_root, cmd_name(a, "DATE"),
                            year, month, dayofmonth, hour, minutes);
                        if (commands_execute(a, cmds)) {
                                inform(a->c, _("The date and time have been set."));
                        }
                        commands_free(cmds);
                } else {
                        inform(a->c, _("Please enter numbers within acceptable ranges "
                                "for year, month, day of month, hour, and minute."));
                }
        }
}

static void
strip_domain(char *hostname, const char *domain)
{
        size_t hlen, dlen;

        if (domain == NULL || domain[0] == '\0')
                return;

        hlen = strlen(hostname);
        dlen = strlen(domain);
        if (hlen > dlen + 1 &&
            hostname[hlen - dlen - 1] == '.' &&
            strcmp(hostname + hlen - dlen, domain) == 0) {
                hostname[hlen - dlen - 1] = '\0';
        }
}

void
fn_assign_hostname_domain(struct i_fn_args *a)
{
        struct dfui_form *f;
        struct dfui_response *r;
        struct dfui_dataset *ds, *new_ds;
        const char *domain, *hostname;
        char *fqdn, buf[MAXHOSTNAMELEN];

        domain = config_var_get(resolv_conf, "search");
        snprintf(buf, sizeof(buf), "%s", config_var_get(rc_conf, "hostname"));
        strip_domain(buf, domain);
        hostname = buf;

        f = dfui_form_create(
            "set_hostname_domain",
            _("Set Hostname/Domain"),
            _("Please enter this machine's hostname and domain name."),
            "",

            "f", "hostname", _("Hostname"),
            _("Enter the Hostname (e.g. `machine')"), "",
            "f", "domain", _("Domain"),
            _("Enter the Domain Name (e.g. `network.lan')"), "",

            "a", "ok", _("OK"), "", "",
            "a", "cancel", _("Cancel"), "", "",
            "p", "accelerator", "ESC",

            NULL
        );

        ds = dfui_dataset_new();
        dfui_dataset_celldata_add(ds, "hostname", hostname);
        dfui_dataset_celldata_add(ds, "domain", domain);
        dfui_form_dataset_add(f, ds);

        if (!dfui_be_present(a->c, f, &r))
                abort_backend();

        if (strcmp(dfui_response_get_action_id(r), "ok") == 0) {
                new_ds = dfui_response_dataset_get_first(r);

                hostname = dfui_dataset_get_value(new_ds, "hostname");
                domain = dfui_dataset_get_value(new_ds, "domain");
                if (strlen(domain) == 0)
                        asprintf(&fqdn, "%s", hostname);
                else
                        asprintf(&fqdn, "%s.%s", hostname, domain);

                config_var_set(rc_conf, "hostname", fqdn);
                free(fqdn);

                config_var_set(resolv_conf, "search", domain);
        }

        dfui_form_free(f);
        dfui_response_free(r);
}

static int
set_interface_router(char *router, size_t len, const char *ip, const char *netmask)
{
        struct in_addr in_ip, in_netmask, in_gw;

        memset(router, 0, len);

        if (ip == NULL || strlen(ip) == 0)
                return(0);
        if (netmask == NULL || strlen(netmask) == 0)
                return(0);

        if (inet_pton(AF_INET, ip, &in_ip) != 1)
                return(-1);
        if (inet_pton(AF_INET, netmask, &in_netmask) != 1)
                return(-1);

        in_gw.s_addr = ntohl(in_ip.s_addr) & ntohl(in_netmask.s_addr);
        in_gw.s_addr = htonl(in_gw.s_addr + 1); /* network + 1 */
        inet_ntop(AF_INET, &in_gw, router, len);

        return(0);
}

static int
cb_field_interface_ip(struct dfui_form *form, struct dfui_field *field __unused,
                      const char *new_value, void *userdata __unused)
{
        struct dfui_dataset *ds;
        const char *netmask;
        char router[INET_ADDRSTRLEN];

        if (new_value == NULL || strlen(new_value) == 0)
                return(0);

        ds = dfui_form_dataset_get_first(form);
        netmask = dfui_dataset_get_value(ds, "interface_netmask");

        if (set_interface_router(router, sizeof(router), new_value, netmask) != 0)
                return(-1);

        dfui_dataset_set_value(ds, "defaultrouter", router);

        return(0);
}

static int
cb_field_interface_netmask(struct dfui_form *form,
                           struct dfui_field *field __unused,
                           const char *new_value, void *userdata __unused)
{
        struct dfui_dataset *ds;
        const char *ip;
        char router[INET_ADDRSTRLEN];

        if (new_value == NULL || strlen(new_value) == 0)
                return(0);

        ds = dfui_form_dataset_get_first(form);
        ip = dfui_dataset_get_value(ds, "interface_ip");

        if (set_interface_router(router, sizeof(router), ip, new_value) != 0)
                return(-1);

        dfui_dataset_set_value(ds, "defaultrouter", router);

        return(0);
}

void
fn_assign_ip(struct i_fn_args *a)
{
        FILE *p;
        struct commands *cmds;
        struct command *cmd;
        struct dfui_dataset *ds, *new_ds;
        struct dfui_form *f;
        struct dfui_field *fi;
        struct dfui_action *k;
        struct dfui_response *r;
        const char *domain, *hostname;
        const char *interface_ip, *interface_netmask, *defaultrouter, *dns_resolver;
        char *string, *string1;
        char *word;
        char interface[256];
        char line[256], buf[MAXHOSTNAMELEN];
        int write_config;

        /*
         * Get interface list.
         */
        p = popen("/sbin/ifconfig -l", "r");
        /* XXX it's possible (though extremely unlikely) this will fail. */
        while (fgets(line, 255, p) != NULL)
                line[strlen(line) - 1] = '\0';

        pclose(p);

        f = dfui_form_create(
            "assign_ip",
            _("Assign IP Address"),
            _("Please select which interface you would like to configure:"),
            "",
            "p",        "role", "menu",
            NULL
        );

        /* Loop through array. */
        word = strtok(line, " \t");
        while (word != NULL) {
                dfui_form_action_add(f, word,
                    dfui_info_new(word, "", ""));
                word = strtok(NULL, " ");
        }

        k = dfui_form_action_add(f, "cancel",
            dfui_info_new("Cancel", "", ""));
        dfui_action_property_set(k, "accelerator", "ESC");

        if (!dfui_be_present(a->c, f, &r))
                abort_backend();

        if (strcmp(dfui_response_get_action_id(r), "cancel") == 0) {
                dfui_form_free(f);
                dfui_response_free(r);
                return;
        }

        strlcpy(interface, dfui_response_get_action_id(r), 256);

        domain = config_var_get(resolv_conf, "search");
        snprintf(buf, sizeof(buf), "%s", config_var_get(rc_conf, "hostname"));
        strip_domain(buf, domain);
        hostname = buf;

        switch (dfui_be_present_dialog(a->c, _("Use DHCP?"),
            _("Use DHCP|Configure Manually"),
            _("DHCP allows the interface to automatically obtain "
            "an IP address from a nearby DHCP server.\n\n"
            "Would you like to enable DHCP for %s?"), interface)) {
        case 1:
                write_config = 0;
                cmds = commands_new();
                cmd = command_add(cmds, "%s%s dhclient",
                    a->os_root, cmd_name(a, "KILLALL"));
                command_set_failure_mode(cmd, COMMAND_FAILURE_IGNORE);
                command_add(cmds, "%s%s %s",
                    a->os_root, cmd_name(a, "DHCLIENT"),
                    interface);
                if (commands_execute(a, cmds)) {
                        /* XXX sleep(3); */
                        show_ifconfig(a->c, interface);
                        write_config = 1;
                } else {
                        switch (dfui_be_present_dialog(a->c, _("DHCP Failure"),
                            _("Yes|No"),
                            _("Warning: could not enable dhclient for %s.\n\n"
                              "Write the corresponding settings to rc.conf "
                              "anyway?"), interface)) {
                        case 1:
                                write_config = 1;
                                break;
                        case 2:
                                write_config = 0;
                                break;
                        default:
                                abort_backend();
                        }
                }
                commands_free(cmds);

                if (write_config) {
                        asprintf(&string, "ifconfig_%s", interface);
                        config_var_set(rc_conf, string, "DHCP");
                        free(string);
                }
                break;
        case 2:
                dfui_form_free(f);
                dfui_response_free(r);
                f = dfui_form_create(
                    "assign_ip",
                    _("Assign IP Address"),
                    _("Configuring Interface:"),
                    "",

                    "f", "interface_ip", _("IP Address"),
                    _("Enter the IP Address you would like to use"), "",
                    "f", "interface_netmask", _("Netmask"),
                    _("Enter the netmask of the IP address"), "",
                    "f", "defaultrouter", _("Default Router"),
                    _("Enter the IP address of the default router"), "",
                    "f", "dns_resolver", _("Primary DNS Server"),
                    _("Enter the IP address of primary DNS Server"), "",
                    "f", "hostname", _("Hostname"),
                    _("Enter the Hostname"), "",
                    "f", "domain", _("Domain"),
                    _("Enter the Domain Name"), "",

                    "a", "ok", _("Configure Interface"),
                    "", "",
                    "a", "cancel", _("Return to Utilities Menu"),
                    "", "",
                    "p", "accelerator", "ESC",

                    NULL
                );

                ds = dfui_dataset_new();
                dfui_dataset_celldata_add(ds, "interface_ip", "");
                dfui_dataset_celldata_add(ds, "interface_netmask",
                    "255.255.255.0");
                dfui_dataset_celldata_add(ds, "defaultrouter", "");
                dfui_dataset_celldata_add(ds, "dns_resolver",
                    config_var_get(resolv_conf, "nameserver"));
                dfui_dataset_celldata_add(ds, "hostname", hostname);
                dfui_dataset_celldata_add(ds, "domain", domain);
                dfui_form_dataset_add(f, ds);

                fi = dfui_form_field_find(f, "interface_ip");
                dfui_field_set_callback(fi, cb_field_interface_ip, NULL);
                fi = dfui_form_field_find(f, "interface_netmask");
                dfui_field_set_callback(fi, cb_field_interface_netmask, NULL);

                if (!dfui_be_present(a->c, f, &r))
                        abort_backend();

                if (strcmp(dfui_response_get_action_id(r), "cancel") == 0)
                        break;

                new_ds = dfui_response_dataset_get_first(r);

                interface_ip = dfui_dataset_get_value(
                                        new_ds, "interface_ip");
                interface_netmask = dfui_dataset_get_value(
                                        new_ds, "interface_netmask");
                defaultrouter = dfui_dataset_get_value(
                                        new_ds, "defaultrouter");
                dns_resolver = dfui_dataset_get_value(
                                        new_ds, "dns_resolver");
                hostname = dfui_dataset_get_value(
                                        new_ds, "hostname");
                domain = dfui_dataset_get_value(
                                        new_ds, "domain");

                cmds = commands_new();
                command_add(cmds, "%s%s %s %s netmask %s",
                    a->os_root, cmd_name(a, "IFCONFIG"),
                    interface, interface_ip, interface_netmask);
                command_add(cmds, "%s%s add default %s",
                    a->os_root, cmd_name(a, "ROUTE"),
                    defaultrouter);

                write_config = 0;
                if (commands_execute(a, cmds)) {
                        /* XXX sleep(3); */
                        show_ifconfig(a->c, interface);
                        write_config = 1;
                } else {
                        switch (dfui_be_present_dialog(a->c,
                            _("ifconfig Failure"),
                            _("Yes|No"),
                            _("Warning: could not assign IP address "
                              "or default gateway.\n\n"
                              "Write the corresponding settings to "
                              "rc.conf anyway?"))) {
                        case 1:
                                write_config = 1;
                                break;
                        case 2:
                                write_config = 0;
                                break;
                        default:
                                abort_backend();
                        }
                }
                commands_free(cmds);

                if (write_config) {
                        asprintf(&string, "ifconfig_%s", interface);
                        asprintf(&string1, "inet %s netmask %s",
                            interface_ip, interface_netmask);
                        config_var_set(rc_conf, string, string1);
                        free(string);
                        free(string1);

                        config_var_set(rc_conf, "defaultrouter", defaultrouter);

                        if (strlen(domain) == 0)
                                asprintf(&string, "%s", hostname);
                        else
                                asprintf(&string, "%s.%s", hostname, domain);
                        config_var_set(rc_conf, "hostname", string);
                        free(string);

                        config_var_set(resolv_conf, "search", domain);
                        config_var_set(resolv_conf, "nameserver", dns_resolver);
                }
                break;
        default:
                abort_backend();
        }

        dfui_form_free(f);
        dfui_response_free(r);
}

/*** NON-fn_ FUNCTIONS ***/

/*
 * Uses ss->selected_{disk,slice} as the target system.
 *
 * XXX We now assume that the root mount has enough of the topology
 *     to handle any configuration actions.
 */
int
mount_target_system(struct i_fn_args *a)
{
        FILE *crypttab, *fstab;
        struct commands *cmds;
        struct command *cmd;
        struct subpartition *a_subpart;
        struct subpartition *d_subpart;
        char name[256], device[256];
        char *filename, line[256];
        char *word;

        /*
         * Mount subpartitions from this installation if they are
         * not already mounted.  Tricky, as we need to honour the
         * installation's loader.conf and fstab.
         */
        cmds = commands_new();

        /*
         * First, unmount anything already mounted on /mnt.
         */
        unmount_all_under(a, cmds, "%smnt", a->os_root);

        /*
         * Reset and clear out subpartitions so that system
         * can make a "dummy" subpart.
         */
        subpartitions_free(storage_get_selected_slice(a->s));

        /*
         * Create a temporary dummy subpartition - that we
         * assume exists
         */
        a_subpart = subpartition_new_ufs(storage_get_selected_slice(a->s),
                                         "/dummy", 0, 0, 0, 0, 0, 0);
        subpartition_new_ufs(storage_get_selected_slice(a->s),
                                         "swap", 0, 0, 0, 0, 0, 0);
        d_subpart = subpartition_new_ufs(storage_get_selected_slice(a->s),
                                         "/dummy", 0, 0, 0, 0, 0, 0);

        /*
         * Mount the target's / and read its /etc/fstab.
         *
         * XXX NEEDS TO BE REWRITTEN XXX
         */
        {
                command_add(cmds, "%s%s /dev/%s %sboot",
                    a->os_root, cmd_name(a, "MOUNT"),
                    subpartition_get_device_name(a_subpart),
                    a->os_root);
                cmd = command_add(cmds,
                    "%s%s -f %st2;"
                    "%s%s \"^vfs\\.root\\.realroot=\" %sboot/loader.conf >%st2",
                    a->os_root, cmd_name(a, "RM"), a->tmp,
                    a->os_root, cmd_name(a, "GREP"),
                    a->os_root, a->tmp);
                command_set_failure_mode(cmd, COMMAND_FAILURE_IGNORE);
        }
        if (!commands_execute(a, cmds)) {
                commands_free(cmds);
                return(0);
        }
        commands_free(cmds);
        cmds = commands_new();

        /*
         * XXX NEEDS TO BE REWRITTEN XXX
         */
        {
                struct stat sb = { .st_size = 0 };
                const char *fsname;
                const char *mountid;

                switch (use_hammer) {
                case 1:
                        fsname = "hammer";
                        mountid = "MOUNT_HAMMER";
                        break;
                case 2:
                        fsname = "hammer2";
                        mountid = "MOUNT_HAMMER2";
                        break;
                case 0:
                default:
                        fsname = "ufs";
                        mountid = "MOUNT";
                        break;
                }

                stat("/tmp/t2", &sb);
                if (sb.st_size > 0) {
                        command_add(cmds, "%s%s %sboot",
                            a->os_root, cmd_name(a, "UMOUNT"),
                            a->os_root);
                        fn_get_passphrase(a, 0);
                        command_add(cmds,
                            "%s%s -d /tmp/t1 luksOpen /dev/`%s%s \"^vfs\\.root\\.realroot=\" %st2 |"
                            "%s%s -F%s: '{print $2;}' |"
                            "%s%s -F: '{print $1;}'` %s",
                            a->os_root, cmd_name(a, "CRYPTSETUP"),
                            a->os_root, cmd_name(a, "GREP"),
                            a->tmp,
                            a->os_root, cmd_name(a, "AWK"),
                            fsname,
                            a->os_root, cmd_name(a, "AWK"),
                            subpartition_get_mapper_name(d_subpart, -1));
                        command_add(cmds,
                            "%s%s %s %s%s",
                            a->os_root,
                            cmd_name(a, mountid),
                            subpartition_get_mapper_name(d_subpart, 1),
                            a->os_root, a->cfg_root);
                } else {
                        command_add(cmds,
                            "%s%s /dev/`%s%s \"^vfs\\.root\\.mountfrom\" %sboot/loader.conf |"
                            "%s%s -F%s: '{print $2;}' |"
                            "%s%s 's/\"//'` %s%s",
                            a->os_root,
                            cmd_name(a, mountid),
                            a->os_root, cmd_name(a, "GREP"),
                            a->os_root,
                            a->os_root, cmd_name(a, "AWK"),
                            fsname,
                            a->os_root, cmd_name(a, "SED"),
                            a->os_root, a->cfg_root);
                        command_add(cmds, "%s%s %sboot",
                            a->os_root, cmd_name(a, "UMOUNT"),
                            a->os_root);
                }
        }
        if (!commands_execute(a, cmds)) {
                commands_free(cmds);
                unlink("/tmp/t1");
                return(0);
        }
        commands_free(cmds);
        cmds = commands_new();

        /*
         * Get rid of the dummy subpartition.
         */
        subpartitions_free(storage_get_selected_slice(a->s));

        /*
         * See if an /etc/crypttab exists.
         *
         * Scan and open the related mappings (currently not used since
         * we removed the additional mounts from the fstab scan, but we
         * might put those back in at a future date so leave this in for
         * now).
         */
        asprintf(&filename, "%s%s/etc/crypttab", a->os_root, a->cfg_root);
        crypttab = fopen(filename, "r");
        free(filename);
        if (crypttab != NULL) {
                while (fgets(line, 256, crypttab) != NULL) {
                        /*
                         * Parse the crypttab line.
                         */
                        if (first_non_space_char_is(line, '#'))
                                continue;
                        if ((word = strtok(line, " \t")) == NULL)
                                continue;
                        strlcpy(name, word, 256);

                        /* don't mount encrypted swap */
                        if (strcmp(name, "swap") == 0)
                                continue;
                        /* encrypted root already mounted */
                        if (strcmp(name, subpartition_get_mapper_name(d_subpart, -1)) == 0)
                                continue;

                        if ((word = strtok(NULL, " \t")) == NULL)
                                continue;
                        strlcpy(device, word, 256);

                        command_add(cmds,
                            "%s%s -d /tmp/t1 luksOpen %s %s",
                            a->os_root, cmd_name(a, "CRYPTSETUP"),
                            device, name);

                        continue;
                }
                fclose(crypttab);
        }
        if (!commands_execute(a, cmds)) {
                commands_free(cmds);
                unlink("/tmp/t1");
                return(0);
        }
        commands_free(cmds);
        unlink("/tmp/t1");

        /*
         * (current we do not mount the other partitions, everything needed
         *  for system configuration should be on the already-mounted root).
         */
        asprintf(&filename, "%s%s/etc/fstab", a->os_root, a->cfg_root);
        fstab = fopen(filename, "r");
        free(filename);
        if (fstab == NULL) {
                inform(a->c, _("Filesystem table on installed system could not be read."));
                cmds = commands_new();
                command_add(cmds, "%s%s %s%s",
                    a->os_root, cmd_name(a, "UMOUNT"),
                    a->os_root, a->cfg_root);
                if (!commands_execute(a, cmds)) {
                        inform(a->c, _("Warning: Installed system was not properly unmounted."));
                }
                commands_free(cmds);
                return(0);
        }
        fclose(fstab);

        return 1;
}