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*) strjoina(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_errno(r, "Failed to read os-release file: %m");
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 return log_error_errno(r, "Failed to query user: %m");
173 log_warning("No data entered, skipping.");
177 r = safe_atou(p, &u);
181 if (u <= 0 || u > strv_length(l)) {
182 log_error("Specified entry number out of range.");
186 log_info("Selected '%s'.", l[u-1]);
198 log_error("Entered data invalid.");
209 static int prompt_locale(void) {
210 _cleanup_strv_free_ char **locales = NULL;
213 if (arg_locale || arg_locale_messages)
216 if (!arg_prompt_locale)
219 r = get_locales(&locales);
221 return log_error_errno(r, "Cannot query locales list: %m");
225 printf("\nAvailable Locales:\n\n");
226 r = show_menu(locales, 3, 22, 60);
232 r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
236 if (isempty(arg_locale))
239 r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
246 static int process_locale(void) {
247 const char *etc_localeconf;
252 etc_localeconf = prefix_roota("/etc/locale.conf");
253 if (faccessat(AT_FDCWD, etc_localeconf, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
256 if (arg_copy_locale && arg_root) {
258 mkdir_parents(etc_localeconf, 0755);
259 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0);
262 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
264 log_info("%s copied.", etc_localeconf);
273 if (!isempty(arg_locale))
274 locales[i++] = strjoina("LANG=", arg_locale);
275 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
276 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
283 mkdir_parents(etc_localeconf, 0755);
284 r = write_env_file(etc_localeconf, locales);
286 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
288 log_info("%s written.", etc_localeconf);
292 static int prompt_timezone(void) {
293 _cleanup_strv_free_ char **zones = NULL;
299 if (!arg_prompt_timezone)
302 r = get_timezones(&zones);
304 return log_error_errno(r, "Cannot query timezone list: %m");
308 printf("\nAvailable Time Zones:\n\n");
309 r = show_menu(zones, 3, 22, 30);
315 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
322 static int process_timezone(void) {
323 const char *etc_localtime, *e;
326 etc_localtime = prefix_roota("/etc/localtime");
327 if (faccessat(AT_FDCWD, etc_localtime, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
330 if (arg_copy_timezone && arg_root) {
331 _cleanup_free_ char *p = NULL;
333 r = readlink_malloc("/etc/localtime", &p);
336 return log_error_errno(r, "Failed to read host timezone: %m");
338 mkdir_parents(etc_localtime, 0755);
339 if (symlink(p, etc_localtime) < 0)
340 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
342 log_info("%s copied.", etc_localtime);
347 r = prompt_timezone();
351 if (isempty(arg_timezone))
354 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
356 mkdir_parents(etc_localtime, 0755);
357 if (symlink(e, etc_localtime) < 0)
358 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
360 log_info("%s written", etc_localtime);
364 static int prompt_hostname(void) {
370 if (!arg_prompt_hostname)
377 _cleanup_free_ char *h = NULL;
379 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET));
381 return log_error_errno(r, "Failed to query hostname: %m");
384 log_warning("No hostname entered, skipping.");
388 if (!hostname_is_valid(h)) {
389 log_error("Specified hostname invalid.");
401 static int process_hostname(void) {
402 const char *etc_hostname;
405 etc_hostname = prefix_roota("/etc/hostname");
406 if (faccessat(AT_FDCWD, etc_hostname, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
409 r = prompt_hostname();
413 if (isempty(arg_hostname))
416 mkdir_parents(etc_hostname, 0755);
417 r = write_string_file(etc_hostname, arg_hostname);
419 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
421 log_info("%s written.", etc_hostname);
425 static int process_machine_id(void) {
426 const char *etc_machine_id;
427 char id[SD_ID128_STRING_MAX];
430 etc_machine_id = prefix_roota("/etc/machine-id");
431 if (faccessat(AT_FDCWD, etc_machine_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
434 if (sd_id128_equal(arg_machine_id, SD_ID128_NULL))
437 mkdir_parents(etc_machine_id, 0755);
438 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id));
440 return log_error_errno(r, "Failed to write machine id: %m");
442 log_info("%s written.", etc_machine_id);
446 static int prompt_root_password(void) {
447 const char *msg1, *msg2, *etc_shadow;
450 if (arg_root_password)
453 if (!arg_prompt_root_password)
456 etc_shadow = prefix_roota("/etc/shadow");
457 if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
463 msg1 = strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
464 msg2 = strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter new root password again: ");
467 _cleanup_free_ char *a = NULL, *b = NULL;
469 r = ask_password_tty(msg1, 0, false, NULL, &a);
471 return log_error_errno(r, "Failed to query root password: %m");
474 log_warning("No password entered, skipping.");
478 r = ask_password_tty(msg2, 0, false, NULL, &b);
480 log_error_errno(r, "Failed to query root password: %m");
486 log_error("Entered passwords did not match, please try again.");
493 arg_root_password = a;
501 static int write_root_shadow(const char *path, const struct spwd *p) {
502 _cleanup_fclose_ FILE *f = NULL;
507 f = fopen(path, "wex");
512 if (putspent(p, f) != 0)
513 return errno ? -errno : -EIO;
515 return fflush_and_check(f);
518 static int process_root_password(void) {
520 static const char table[] =
521 "abcdefghijklmnopqrstuvwxyz"
522 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
527 .sp_namp = (char*) "root",
533 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
536 _cleanup_close_ int lock = -1;
542 const char *etc_shadow;
545 etc_shadow = prefix_roota("/etc/shadow");
546 if (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
549 mkdir_parents(etc_shadow, 0755);
551 lock = take_password_lock(arg_root);
555 if (arg_copy_root_password && arg_root) {
559 p = getspnam("root");
560 if (p || errno != ENOENT) {
565 log_error_errno(errno, "Failed to find shadow entry for root: %m");
569 r = write_root_shadow(etc_shadow, p);
571 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
573 log_info("%s copied.", etc_shadow);
578 r = prompt_root_password();
582 if (!arg_root_password)
585 r = dev_urandom(raw, 16);
587 return log_error_errno(r, "Failed to get salt: %m");
589 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
590 assert_cc(sizeof(table) == 64 + 1);
591 j = stpcpy(salt, "$6$");
592 for (i = 0; i < 16; i++)
593 j[i] = table[raw[i] & 63];
598 item.sp_pwdp = crypt(arg_root_password, salt);
603 log_error_errno(errno, "Failed to encrypt password: %m");
607 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
609 r = write_root_shadow(etc_shadow, &item);
611 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
613 log_info("%s written.", etc_shadow);
617 static void help(void) {
618 printf("%s [OPTIONS...]\n\n"
619 "Configures basic settings of the system.\n\n"
620 " -h --help Show this help\n"
621 " --version Show package version\n"
622 " --root=PATH Operate on an alternate filesystem root\n"
623 " --locale=LOCALE Set primary locale (LANG=)\n"
624 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
625 " --timezone=TIMEZONE Set timezone\n"
626 " --hostname=NAME Set host name\n"
627 " --machine-ID=ID Set machine ID\n"
628 " --root-password=PASSWORD Set root password\n"
629 " --root-password-file=FILE Set root password from file\n"
630 " --prompt-locale Prompt the user for locale settings\n"
631 " --prompt-timezone Prompt the user for timezone\n"
632 " --prompt-hostname Prompt the user for hostname\n"
633 " --prompt-root-password Prompt the user for root password\n"
634 " --prompt Prompt for all of the above\n"
635 " --copy-locale Copy locale from host\n"
636 " --copy-timezone Copy timezone from host\n"
637 " --copy-root-password Copy root password from host\n"
638 " --copy Copy locale, timezone, root password\n"
639 " --setup-machine-id Generate a new random machine ID\n"
640 , program_invocation_short_name);
643 static int parse_argv(int argc, char *argv[]) {
654 ARG_ROOT_PASSWORD_FILE,
659 ARG_PROMPT_ROOT_PASSWORD,
663 ARG_COPY_ROOT_PASSWORD,
664 ARG_SETUP_MACHINE_ID,
667 static const struct option options[] = {
668 { "help", no_argument, NULL, 'h' },
669 { "version", no_argument, NULL, ARG_VERSION },
670 { "root", required_argument, NULL, ARG_ROOT },
671 { "locale", required_argument, NULL, ARG_LOCALE },
672 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
673 { "timezone", required_argument, NULL, ARG_TIMEZONE },
674 { "hostname", required_argument, NULL, ARG_HOSTNAME },
675 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
676 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
677 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
678 { "prompt", no_argument, NULL, ARG_PROMPT },
679 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
680 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
681 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
682 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
683 { "copy", no_argument, NULL, ARG_COPY },
684 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
685 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
686 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
687 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
696 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
705 puts(PACKAGE_STRING);
706 puts(SYSTEMD_FEATURES);
711 arg_root = path_make_absolute_cwd(optarg);
715 path_kill_slashes(arg_root);
717 if (path_equal(arg_root, "/")) {
725 if (!locale_is_valid(optarg)) {
726 log_error("Locale %s is not valid.", optarg);
731 arg_locale = strdup(optarg);
737 case ARG_LOCALE_MESSAGES:
738 if (!locale_is_valid(optarg)) {
739 log_error("Locale %s is not valid.", optarg);
743 free(arg_locale_messages);
744 arg_locale_messages = strdup(optarg);
745 if (!arg_locale_messages)
751 if (!timezone_is_valid(optarg)) {
752 log_error("Timezone %s is not valid.", optarg);
757 arg_timezone = strdup(optarg);
763 case ARG_ROOT_PASSWORD:
764 free(arg_root_password);
765 arg_root_password = strdup(optarg);
766 if (!arg_root_password)
771 case ARG_ROOT_PASSWORD_FILE:
772 free(arg_root_password);
773 arg_root_password = NULL;
775 r = read_one_line_file(optarg, &arg_root_password);
777 return log_error_errno(r, "Failed to read %s: %m", optarg);
782 if (!hostname_is_valid(optarg)) {
783 log_error("Host name %s is not valid.", optarg);
788 arg_hostname = strdup(optarg);
795 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
796 log_error("Failed to parse machine id %s.", optarg);
803 arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
806 case ARG_PROMPT_LOCALE:
807 arg_prompt_locale = true;
810 case ARG_PROMPT_TIMEZONE:
811 arg_prompt_timezone = true;
814 case ARG_PROMPT_HOSTNAME:
815 arg_prompt_hostname = true;
818 case ARG_PROMPT_ROOT_PASSWORD:
819 arg_prompt_root_password = true;
823 arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true;
826 case ARG_COPY_LOCALE:
827 arg_copy_locale = true;
830 case ARG_COPY_TIMEZONE:
831 arg_copy_timezone = true;
834 case ARG_COPY_ROOT_PASSWORD:
835 arg_copy_root_password = true;
838 case ARG_SETUP_MACHINE_ID:
840 r = sd_id128_randomize(&arg_machine_id);
842 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
850 assert_not_reached("Unhandled option");
856 int main(int argc, char *argv[]) {
859 r = parse_argv(argc, argv);
863 log_set_target(LOG_TARGET_AUTO);
864 log_parse_environment();
869 r = process_locale();
873 r = process_timezone();
877 r = process_hostname();
881 r = process_machine_id();
885 r = process_root_password();
892 free(arg_locale_messages);
895 clear_string(arg_root_password);
896 free(arg_root_password);
898 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;