1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
33 #include "time-util.h"
34 #include "path-util.h"
35 #include "locale-util.h"
36 #include "ask-password-api.h"
38 static char *arg_root = NULL;
39 static char *arg_locale = NULL; /* $LANG */
40 static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
41 static char *arg_timezone = NULL;
42 static char *arg_hostname = NULL;
43 static sd_id128_t arg_machine_id = {};
44 static char *arg_root_password = NULL;
45 static bool arg_prompt_locale = false;
46 static bool arg_prompt_timezone = false;
47 static bool arg_prompt_hostname = false;
48 static bool arg_prompt_root_password = false;
49 static bool arg_copy_locale = false;
50 static bool arg_copy_timezone = false;
51 static bool arg_copy_root_password = false;
53 #define prefix_roota(p) (arg_root ? (const char*) strappenda(arg_root, p) : (const char*) p)
55 static void clear_string(char *x) {
60 /* A delicious drop of snake-oil! */
61 memset(x, 'x', strlen(x));
64 static bool press_any_key(void) {
68 printf("-- Press any key to proceed --");
71 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
79 static void print_welcome(void) {
80 _cleanup_free_ char *pretty_name = NULL;
81 const char *os_release = NULL;
82 static bool done = false;
88 os_release = prefix_roota("/etc/os-release");
89 r = parse_env_file(os_release, NEWLINE,
90 "PRETTY_NAME", &pretty_name,
94 os_release = prefix_roota("/usr/lib/os-release");
95 r = parse_env_file(os_release, NEWLINE,
96 "PRETTY_NAME", &pretty_name,
100 if (r < 0 && r != -ENOENT)
101 log_warning("Failed to read os-release file: %s", strerror(-r));
103 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
104 isempty(pretty_name) ? "Linux" : pretty_name);
111 static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
112 unsigned n, per_column, i, j;
113 unsigned break_lines, break_modulo;
115 assert(n_columns > 0);
118 per_column = (n + n_columns - 1) / n_columns;
120 break_lines = lines();
124 /* The first page gets two extra lines, since we want to show
126 break_modulo = break_lines;
127 if (break_modulo > 3)
130 for (i = 0; i < per_column; i++) {
132 for (j = 0; j < n_columns; j ++) {
133 _cleanup_free_ char *e = NULL;
135 if (j * per_column + i >= n)
138 e = ellipsize(x[j * per_column + i], width, percentage);
142 printf("%4u) %-*s", j * per_column + i + 1, width, e);
147 /* on the first screen we reserve 2 extra lines for the title */
148 if (i % break_lines == break_modulo) {
149 if (!press_any_key())
157 static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) {
165 _cleanup_free_ char *p = NULL;
168 r = ask_string(&p, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET), text);
170 log_error("Failed to query user: %s", strerror(-r));
175 log_warning("No data entered, skipping.");
179 r = safe_atou(p, &u);
183 if (u <= 0 || u > strv_length(l)) {
184 log_error("Specified entry number out of range.");
188 log_info("Selected '%s'.", l[u-1]);
200 log_error("Entered data invalid.");
211 static int prompt_locale(void) {
212 _cleanup_strv_free_ char **locales = NULL;
215 if (arg_locale || arg_locale_messages)
218 if (!arg_prompt_locale)
221 r = get_locales(&locales);
223 log_error("Cannot query locales list: %s", strerror(-r));
229 printf("\nAvailable Locales:\n\n");
230 r = show_menu(locales, 3, 22, 60);
236 r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
240 if (isempty(arg_locale))
243 r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
250 static int process_locale(void) {
251 const char *etc_localeconf;
256 etc_localeconf = prefix_roota("/etc/locale.conf");
257 if (faccessat(AT_FDCWD, etc_localeconf, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
260 if (arg_copy_locale && arg_root) {
262 mkdir_parents(etc_localeconf, 0755);
263 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644);
266 log_error("Failed to copy %s: %s", etc_localeconf, strerror(-r));
270 log_info("%s copied.", etc_localeconf);
279 if (!isempty(arg_locale))
280 locales[i++] = strappenda("LANG=", arg_locale);
281 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
282 locales[i++] = strappenda("LC_MESSAGES=", arg_locale_messages);
289 mkdir_parents(etc_localeconf, 0755);
290 r = write_env_file(etc_localeconf, locales);
292 log_error("Failed to write %s: %s", etc_localeconf, strerror(-r));
296 log_info("%s written.", etc_localeconf);
300 static int prompt_timezone(void) {
301 _cleanup_strv_free_ char **zones = NULL;
307 if (!arg_prompt_timezone)
310 r = get_timezones(&zones);
312 log_error("Cannot query timezone list: %s", strerror(-r));
318 printf("\nAvailable Time Zones:\n\n");
319 r = show_menu(zones, 3, 22, 30);
325 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
332 static int process_timezone(void) {
333 const char *etc_localtime, *e;
336 etc_localtime = prefix_roota("/etc/localtime");
337 if (faccessat(AT_FDCWD, etc_localtime, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
340 if (arg_copy_timezone && arg_root) {
341 _cleanup_free_ char *p = NULL;
343 r = readlink_malloc("/etc/localtime", &p);
346 log_error("Failed to read host timezone: %s", strerror(-r));
350 mkdir_parents(etc_localtime, 0755);
351 if (symlink(p, etc_localtime) < 0) {
352 log_error("Failed to create %s symlink: %m", etc_localtime);
356 log_info("%s copied.", etc_localtime);
361 r = prompt_timezone();
365 if (isempty(arg_timezone))
368 e = strappenda("../usr/share/zoneinfo/", arg_timezone);
370 mkdir_parents(etc_localtime, 0755);
371 if (symlink(e, etc_localtime) < 0) {
372 log_error("Failed to create %s symlink: %m", etc_localtime);
376 log_info("%s written", etc_localtime);
380 static int prompt_hostname(void) {
386 if (!arg_prompt_hostname)
393 _cleanup_free_ char *h = NULL;
395 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET));
397 log_error("Failed to query hostname: %s", strerror(-r));
402 log_warning("No hostname entered, skipping.");
406 if (!hostname_is_valid(h)) {
407 log_error("Specified hostname invalid.");
419 static int process_hostname(void) {
420 const char *etc_hostname;
423 etc_hostname = prefix_roota("/etc/hostname");
424 if (faccessat(AT_FDCWD, etc_hostname, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
427 r = prompt_hostname();
431 if (isempty(arg_hostname))
434 mkdir_parents(etc_hostname, 0755);
435 r = write_string_file(etc_hostname, arg_hostname);
437 log_error("Failed to write %s: %s", etc_hostname, strerror(-r));
441 log_info("%s written.", etc_hostname);
445 static int process_machine_id(void) {
446 const char *etc_machine_id;
447 char id[SD_ID128_STRING_MAX];
450 etc_machine_id = prefix_roota("/etc/machine-id");
451 if (faccessat(AT_FDCWD, etc_machine_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
454 if (sd_id128_equal(arg_machine_id, SD_ID128_NULL))
457 mkdir_parents(etc_machine_id, 0755);
458 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id));
460 log_error("Failed to write machine id: %s", strerror(-r));
464 log_info("%s written.", etc_machine_id);
468 static int prompt_root_password(void) {
469 const char *msg1, *msg2, *etc_shadow;
472 if (arg_root_password)
475 if (!arg_prompt_root_password)
478 etc_shadow = prefix_roota("/etc/shadow");
479 if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
485 msg1 = strappenda(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
486 msg2 = strappenda(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter new root password again: ");
489 _cleanup_free_ char *a = NULL, *b = NULL;
491 r = ask_password_tty(msg1, 0, false, NULL, &a);
493 log_error("Failed to query root password: %s", strerror(-r));
498 log_warning("No password entered, skipping.");
502 r = ask_password_tty(msg2, 0, false, NULL, &b);
504 log_error("Failed to query root password: %s", strerror(-r));
510 log_error("Entered passwords did not match, please try again.");
517 arg_root_password = a;
525 static int write_root_shadow(const char *path, const struct spwd *p) {
526 _cleanup_fclose_ FILE *f = NULL;
531 f = fopen(path, "wex");
536 if (putspent(p, f) != 0)
537 return errno ? -errno : -EIO;
539 return fflush_and_check(f);
542 static int process_root_password(void) {
544 static const char table[] =
545 "abcdefghijklmnopqrstuvwxyz"
546 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
551 .sp_namp = (char*) "root",
557 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
560 _cleanup_close_ int lock = -1;
566 const char *etc_shadow;
569 etc_shadow = prefix_roota("/etc/shadow");
570 if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
573 mkdir_parents(etc_shadow, 0755);
575 lock = take_password_lock(arg_root);
579 if (arg_copy_root_password && arg_root) {
583 p = getspnam("root");
584 if (p || errno != ENOENT) {
589 log_error("Failed to find shadow entry for root: %m");
593 r = write_root_shadow(etc_shadow, p);
595 log_error("Failed to write %s: %s", etc_shadow, strerror(-r));
599 log_info("%s copied.", etc_shadow);
604 r = prompt_root_password();
608 if (!arg_root_password)
611 r = dev_urandom(raw, 16);
613 log_error("Failed to get salt: %s", strerror(-r));
617 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
618 assert_cc(sizeof(table) == 64 + 1);
619 j = stpcpy(salt, "$6$");
620 for (i = 0; i < 16; i++)
621 j[i] = table[raw[i] & 63];
626 item.sp_pwdp = crypt(arg_root_password, salt);
631 log_error("Failed to encrypt password: %m");
635 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
637 r = write_root_shadow(etc_shadow, &item);
639 log_error("Failed to write %s: %s", etc_shadow, strerror(-r));
643 log_info("%s written.", etc_shadow);
647 static void help(void) {
648 printf("%s [OPTIONS...]\n\n"
649 "Configures basic settings of the system.\n\n"
650 " -h --help Show this help\n"
651 " --version Show package version\n"
652 " --root=PATH Operate on an alternate filesystem root\n"
653 " --locale=LOCALE Set primary locale (LANG=)\n"
654 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
655 " --timezone=TIMEZONE Set timezone\n"
656 " --hostname=NAME Set host name\n"
657 " --machine-ID=ID Set machine ID\n"
658 " --root-password=PASSWORD Set root password\n"
659 " --root-password-file=FILE Set root password from file\n"
660 " --prompt-locale Prompt the user for locale settings\n"
661 " --prompt-timezone Prompt the user for timezone\n"
662 " --prompt-hostname Prompt the user for hostname\n"
663 " --prompt-root-password Prompt the user for root password\n"
664 " --prompt Prompt for locale, timezone, hostname, root password\n"
665 " --copy-locale Copy locale from host\n"
666 " --copy-timezone Copy timezone from host\n"
667 " --copy-root-password Copy root password from host\n"
668 " --copy Copy locale, timezone, root password\n"
669 " --setup-machine-id Generate a new random machine ID\n"
670 , program_invocation_short_name);
673 static int parse_argv(int argc, char *argv[]) {
684 ARG_ROOT_PASSWORD_FILE,
689 ARG_PROMPT_ROOT_PASSWORD,
693 ARG_COPY_ROOT_PASSWORD,
694 ARG_SETUP_MACHINE_ID,
697 static const struct option options[] = {
698 { "help", no_argument, NULL, 'h' },
699 { "version", no_argument, NULL, ARG_VERSION },
700 { "root", required_argument, NULL, ARG_ROOT },
701 { "locale", required_argument, NULL, ARG_LOCALE },
702 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
703 { "timezone", required_argument, NULL, ARG_TIMEZONE },
704 { "hostname", required_argument, NULL, ARG_HOSTNAME },
705 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
706 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
707 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
708 { "prompt", no_argument, NULL, ARG_PROMPT },
709 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
710 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
711 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
712 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
713 { "copy", no_argument, NULL, ARG_COPY },
714 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
715 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
716 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
717 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
726 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
735 puts(PACKAGE_STRING);
736 puts(SYSTEMD_FEATURES);
741 arg_root = path_make_absolute_cwd(optarg);
745 path_kill_slashes(arg_root);
747 if (path_equal(arg_root, "/")) {
755 if (!locale_is_valid(optarg)) {
756 log_error("Locale %s is not valid.", optarg);
761 arg_locale = strdup(optarg);
767 case ARG_LOCALE_MESSAGES:
768 if (!locale_is_valid(optarg)) {
769 log_error("Locale %s is not valid.", optarg);
773 free(arg_locale_messages);
774 arg_locale_messages = strdup(optarg);
775 if (!arg_locale_messages)
781 if (!timezone_is_valid(optarg)) {
782 log_error("Timezone %s is not valid.", optarg);
787 arg_timezone = strdup(optarg);
793 case ARG_ROOT_PASSWORD:
794 free(arg_root_password);
795 arg_root_password = strdup(optarg);
796 if (!arg_root_password)
801 case ARG_ROOT_PASSWORD_FILE:
802 free(arg_root_password);
803 arg_root_password = NULL;
805 r = read_one_line_file(optarg, &arg_root_password);
807 log_error("Failed to read %s: %s", optarg, strerror(-r));
814 if (!hostname_is_valid(optarg)) {
815 log_error("Host name %s is not valid.", optarg);
820 arg_hostname = strdup(optarg);
827 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
828 log_error("Failed to parse machine id %s.", optarg);
835 arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
838 case ARG_PROMPT_LOCALE:
839 arg_prompt_locale = true;
842 case ARG_PROMPT_TIMEZONE:
843 arg_prompt_timezone = true;
846 case ARG_PROMPT_HOSTNAME:
847 arg_prompt_hostname = true;
850 case ARG_PROMPT_ROOT_PASSWORD:
851 arg_prompt_root_password = true;
855 arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true;
858 case ARG_COPY_LOCALE:
859 arg_copy_locale = true;
862 case ARG_COPY_TIMEZONE:
863 arg_copy_timezone = true;
866 case ARG_COPY_ROOT_PASSWORD:
867 arg_copy_root_password = true;
870 case ARG_SETUP_MACHINE_ID:
872 r = sd_id128_randomize(&arg_machine_id);
874 log_error("Failed to generate randomized machine ID: %s", strerror(-r));
884 assert_not_reached("Unhandled option");
890 int main(int argc, char *argv[]) {
893 r = parse_argv(argc, argv);
897 log_set_target(LOG_TARGET_AUTO);
898 log_parse_environment();
903 r = process_locale();
907 r = process_timezone();
911 r = process_hostname();
915 r = process_machine_id();
919 r = process_root_password();
926 free(arg_locale_messages);
929 clear_string(arg_root_password);
930 free(arg_root_password);
932 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;