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 "random-util.h"
36 #include "locale-util.h"
37 #include "ask-password-api.h"
38 #include "terminal-util.h"
39 #include "hostname-util.h"
41 static char *arg_root
= NULL
;
42 static char *arg_locale
= NULL
; /* $LANG */
43 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
44 static char *arg_timezone
= NULL
;
45 static char *arg_hostname
= NULL
;
46 static sd_id128_t arg_machine_id
= {};
47 static char *arg_root_password
= NULL
;
48 static bool arg_prompt_locale
= false;
49 static bool arg_prompt_timezone
= false;
50 static bool arg_prompt_hostname
= false;
51 static bool arg_prompt_root_password
= false;
52 static bool arg_copy_locale
= false;
53 static bool arg_copy_timezone
= false;
54 static bool arg_copy_root_password
= false;
56 static void clear_string(char *x
) {
61 /* A delicious drop of snake-oil! */
62 memset(x
, 'x', strlen(x
));
65 static bool press_any_key(void) {
69 printf("-- Press any key to proceed --");
72 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
80 static void print_welcome(void) {
81 _cleanup_free_
char *pretty_name
= NULL
;
82 const char *os_release
= NULL
;
83 static bool done
= false;
89 os_release
= prefix_roota(arg_root
, "/etc/os-release");
90 r
= parse_env_file(os_release
, NEWLINE
,
91 "PRETTY_NAME", &pretty_name
,
95 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
96 r
= parse_env_file(os_release
, NEWLINE
,
97 "PRETTY_NAME", &pretty_name
,
101 if (r
< 0 && r
!= -ENOENT
)
102 log_warning_errno(r
, "Failed to read os-release file: %m");
104 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
105 isempty(pretty_name
) ? "Linux" : pretty_name
);
112 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
113 unsigned n
, per_column
, i
, j
;
114 unsigned break_lines
, break_modulo
;
116 assert(n_columns
> 0);
119 per_column
= (n
+ n_columns
- 1) / n_columns
;
121 break_lines
= lines();
125 /* The first page gets two extra lines, since we want to show
127 break_modulo
= break_lines
;
128 if (break_modulo
> 3)
131 for (i
= 0; i
< per_column
; i
++) {
133 for (j
= 0; j
< n_columns
; j
++) {
134 _cleanup_free_
char *e
= NULL
;
136 if (j
* per_column
+ i
>= n
)
139 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
143 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
148 /* on the first screen we reserve 2 extra lines for the title */
149 if (i
% break_lines
== break_modulo
) {
150 if (!press_any_key())
158 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
166 _cleanup_free_
char *p
= NULL
;
169 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
171 return log_error_errno(r
, "Failed to query user: %m");
174 log_warning("No data entered, skipping.");
178 r
= safe_atou(p
, &u
);
182 if (u
<= 0 || u
> strv_length(l
)) {
183 log_error("Specified entry number out of range.");
187 log_info("Selected '%s'.", l
[u
-1]);
199 log_error("Entered data invalid.");
210 static int prompt_locale(void) {
211 _cleanup_strv_free_
char **locales
= NULL
;
214 if (arg_locale
|| arg_locale_messages
)
217 if (!arg_prompt_locale
)
220 r
= get_locales(&locales
);
222 return log_error_errno(r
, "Cannot query locales list: %m");
226 printf("\nAvailable Locales:\n\n");
227 r
= show_menu(locales
, 3, 22, 60);
233 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
237 if (isempty(arg_locale
))
240 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
247 static int process_locale(void) {
248 const char *etc_localeconf
;
253 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
254 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
257 if (arg_copy_locale
&& arg_root
) {
259 mkdir_parents(etc_localeconf
, 0755);
260 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
263 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
265 log_info("%s copied.", etc_localeconf
);
274 if (!isempty(arg_locale
))
275 locales
[i
++] = strjoina("LANG=", arg_locale
);
276 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
277 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
284 mkdir_parents(etc_localeconf
, 0755);
285 r
= write_env_file(etc_localeconf
, locales
);
287 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
289 log_info("%s written.", etc_localeconf
);
293 static int prompt_timezone(void) {
294 _cleanup_strv_free_
char **zones
= NULL
;
300 if (!arg_prompt_timezone
)
303 r
= get_timezones(&zones
);
305 return log_error_errno(r
, "Cannot query timezone list: %m");
309 printf("\nAvailable Time Zones:\n\n");
310 r
= show_menu(zones
, 3, 22, 30);
316 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
323 static int process_timezone(void) {
324 const char *etc_localtime
, *e
;
327 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
328 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
331 if (arg_copy_timezone
&& arg_root
) {
332 _cleanup_free_
char *p
= NULL
;
334 r
= readlink_malloc("/etc/localtime", &p
);
337 return log_error_errno(r
, "Failed to read host timezone: %m");
339 mkdir_parents(etc_localtime
, 0755);
340 if (symlink(p
, etc_localtime
) < 0)
341 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
343 log_info("%s copied.", etc_localtime
);
348 r
= prompt_timezone();
352 if (isempty(arg_timezone
))
355 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
357 mkdir_parents(etc_localtime
, 0755);
358 if (symlink(e
, etc_localtime
) < 0)
359 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
361 log_info("%s written", etc_localtime
);
365 static int prompt_hostname(void) {
371 if (!arg_prompt_hostname
)
378 _cleanup_free_
char *h
= NULL
;
380 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
382 return log_error_errno(r
, "Failed to query hostname: %m");
385 log_warning("No hostname entered, skipping.");
389 if (!hostname_is_valid(h
, true)) {
390 log_error("Specified hostname invalid.");
394 /* Get rid of the trailing dot that we allow, but don't want to see */
395 arg_hostname
= hostname_cleanup(h
, false);
403 static int process_hostname(void) {
404 const char *etc_hostname
;
407 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
408 if (faccessat(AT_FDCWD
, etc_hostname
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
411 r
= prompt_hostname();
415 if (isempty(arg_hostname
))
418 mkdir_parents(etc_hostname
, 0755);
419 r
= write_string_file(etc_hostname
, arg_hostname
, WRITE_STRING_FILE_CREATE
);
421 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
423 log_info("%s written.", etc_hostname
);
427 static int process_machine_id(void) {
428 const char *etc_machine_id
;
429 char id
[SD_ID128_STRING_MAX
];
432 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
433 if (faccessat(AT_FDCWD
, etc_machine_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
436 if (sd_id128_equal(arg_machine_id
, SD_ID128_NULL
))
439 mkdir_parents(etc_machine_id
, 0755);
440 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
), WRITE_STRING_FILE_CREATE
);
442 return log_error_errno(r
, "Failed to write machine id: %m");
444 log_info("%s written.", etc_machine_id
);
448 static int prompt_root_password(void) {
449 const char *msg1
, *msg2
, *etc_shadow
;
452 if (arg_root_password
)
455 if (!arg_prompt_root_password
)
458 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
459 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
465 msg1
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
466 msg2
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter new root password again: ");
469 _cleanup_free_
char *a
= NULL
, *b
= NULL
;
471 r
= ask_password_tty(msg1
, 0, false, NULL
, &a
);
473 return log_error_errno(r
, "Failed to query root password: %m");
476 log_warning("No password entered, skipping.");
480 r
= ask_password_tty(msg2
, 0, false, NULL
, &b
);
482 log_error_errno(r
, "Failed to query root password: %m");
488 log_error("Entered passwords did not match, please try again.");
495 arg_root_password
= a
;
503 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
504 _cleanup_fclose_
FILE *f
= NULL
;
509 f
= fopen(path
, "wex");
514 if (putspent(p
, f
) != 0)
515 return errno
? -errno
: -EIO
;
517 return fflush_and_check(f
);
520 static int process_root_password(void) {
522 static const char table
[] =
523 "abcdefghijklmnopqrstuvwxyz"
524 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
529 .sp_namp
= (char*) "root",
535 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
538 _cleanup_close_
int lock
= -1;
544 const char *etc_shadow
;
547 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
548 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
551 mkdir_parents(etc_shadow
, 0755);
553 lock
= take_password_lock(arg_root
);
557 if (arg_copy_root_password
&& arg_root
) {
561 p
= getspnam("root");
562 if (p
|| errno
!= ENOENT
) {
567 log_error_errno(errno
, "Failed to find shadow entry for root: %m");
571 r
= write_root_shadow(etc_shadow
, p
);
573 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
575 log_info("%s copied.", etc_shadow
);
580 r
= prompt_root_password();
584 if (!arg_root_password
)
587 r
= dev_urandom(raw
, 16);
589 return log_error_errno(r
, "Failed to get salt: %m");
591 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
592 assert_cc(sizeof(table
) == 64 + 1);
593 j
= stpcpy(salt
, "$6$");
594 for (i
= 0; i
< 16; i
++)
595 j
[i
] = table
[raw
[i
] & 63];
600 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
605 log_error_errno(errno
, "Failed to encrypt password: %m");
609 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
611 r
= write_root_shadow(etc_shadow
, &item
);
613 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
615 log_info("%s written.", etc_shadow
);
619 static void help(void) {
620 printf("%s [OPTIONS...]\n\n"
621 "Configures basic settings of the system.\n\n"
622 " -h --help Show this help\n"
623 " --version Show package version\n"
624 " --root=PATH Operate on an alternate filesystem root\n"
625 " --locale=LOCALE Set primary locale (LANG=)\n"
626 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
627 " --timezone=TIMEZONE Set timezone\n"
628 " --hostname=NAME Set host name\n"
629 " --machine-ID=ID Set machine ID\n"
630 " --root-password=PASSWORD Set root password\n"
631 " --root-password-file=FILE Set root password from file\n"
632 " --prompt-locale Prompt the user for locale settings\n"
633 " --prompt-timezone Prompt the user for timezone\n"
634 " --prompt-hostname Prompt the user for hostname\n"
635 " --prompt-root-password Prompt the user for root password\n"
636 " --prompt Prompt for all of the above\n"
637 " --copy-locale Copy locale from host\n"
638 " --copy-timezone Copy timezone from host\n"
639 " --copy-root-password Copy root password from host\n"
640 " --copy Copy locale, timezone, root password\n"
641 " --setup-machine-id Generate a new random machine ID\n"
642 , program_invocation_short_name
);
645 static int parse_argv(int argc
, char *argv
[]) {
656 ARG_ROOT_PASSWORD_FILE
,
661 ARG_PROMPT_ROOT_PASSWORD
,
665 ARG_COPY_ROOT_PASSWORD
,
666 ARG_SETUP_MACHINE_ID
,
669 static const struct option options
[] = {
670 { "help", no_argument
, NULL
, 'h' },
671 { "version", no_argument
, NULL
, ARG_VERSION
},
672 { "root", required_argument
, NULL
, ARG_ROOT
},
673 { "locale", required_argument
, NULL
, ARG_LOCALE
},
674 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
675 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
676 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
677 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
678 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
679 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
680 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
681 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
682 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
683 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
684 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
685 { "copy", no_argument
, NULL
, ARG_COPY
},
686 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
687 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
688 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
689 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
698 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
707 puts(PACKAGE_STRING
);
708 puts(SYSTEMD_FEATURES
);
713 arg_root
= path_make_absolute_cwd(optarg
);
717 path_kill_slashes(arg_root
);
719 if (path_equal(arg_root
, "/")) {
727 if (!locale_is_valid(optarg
)) {
728 log_error("Locale %s is not valid.", optarg
);
733 arg_locale
= strdup(optarg
);
739 case ARG_LOCALE_MESSAGES
:
740 if (!locale_is_valid(optarg
)) {
741 log_error("Locale %s is not valid.", optarg
);
745 free(arg_locale_messages
);
746 arg_locale_messages
= strdup(optarg
);
747 if (!arg_locale_messages
)
753 if (!timezone_is_valid(optarg
)) {
754 log_error("Timezone %s is not valid.", optarg
);
759 arg_timezone
= strdup(optarg
);
765 case ARG_ROOT_PASSWORD
:
766 free(arg_root_password
);
767 arg_root_password
= strdup(optarg
);
768 if (!arg_root_password
)
773 case ARG_ROOT_PASSWORD_FILE
:
774 free(arg_root_password
);
775 arg_root_password
= NULL
;
777 r
= read_one_line_file(optarg
, &arg_root_password
);
779 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
784 if (!hostname_is_valid(optarg
, true)) {
785 log_error("Host name %s is not valid.", optarg
);
789 hostname_cleanup(optarg
, false);
790 if (free_and_strdup(&arg_hostname
, optarg
) < 0)
796 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
797 log_error("Failed to parse machine id %s.", optarg
);
804 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
807 case ARG_PROMPT_LOCALE
:
808 arg_prompt_locale
= true;
811 case ARG_PROMPT_TIMEZONE
:
812 arg_prompt_timezone
= true;
815 case ARG_PROMPT_HOSTNAME
:
816 arg_prompt_hostname
= true;
819 case ARG_PROMPT_ROOT_PASSWORD
:
820 arg_prompt_root_password
= true;
824 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
827 case ARG_COPY_LOCALE
:
828 arg_copy_locale
= true;
831 case ARG_COPY_TIMEZONE
:
832 arg_copy_timezone
= true;
835 case ARG_COPY_ROOT_PASSWORD
:
836 arg_copy_root_password
= true;
839 case ARG_SETUP_MACHINE_ID
:
841 r
= sd_id128_randomize(&arg_machine_id
);
843 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
851 assert_not_reached("Unhandled option");
857 int main(int argc
, char *argv
[]) {
860 r
= parse_argv(argc
, argv
);
864 log_set_target(LOG_TARGET_AUTO
);
865 log_parse_environment();
870 r
= process_locale();
874 r
= process_timezone();
878 r
= process_hostname();
882 r
= process_machine_id();
886 r
= process_root_password();
893 free(arg_locale_messages
);
896 clear_string(arg_root_password
);
897 free(arg_root_password
);
899 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;