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/>.
27 #include "alloc-util.h"
28 #include "ask-password-api.h"
33 #include "hostname-util.h"
34 #include "locale-util.h"
36 #include "parse-util.h"
37 #include "path-util.h"
38 #include "random-util.h"
39 #include "string-util.h"
41 #include "terminal-util.h"
42 #include "time-util.h"
43 #include "umask-util.h"
44 #include "user-util.h"
46 static char *arg_root
= NULL
;
47 static char *arg_locale
= NULL
; /* $LANG */
48 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
49 static char *arg_timezone
= NULL
;
50 static char *arg_hostname
= NULL
;
51 static sd_id128_t arg_machine_id
= {};
52 static char *arg_root_password
= NULL
;
53 static bool arg_prompt_locale
= false;
54 static bool arg_prompt_timezone
= false;
55 static bool arg_prompt_hostname
= false;
56 static bool arg_prompt_root_password
= false;
57 static bool arg_copy_locale
= false;
58 static bool arg_copy_timezone
= false;
59 static bool arg_copy_root_password
= false;
61 static bool press_any_key(void) {
65 printf("-- Press any key to proceed --");
68 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
76 static void print_welcome(void) {
77 _cleanup_free_
char *pretty_name
= NULL
;
78 const char *os_release
= NULL
;
79 static bool done
= false;
85 os_release
= prefix_roota(arg_root
, "/etc/os-release");
86 r
= parse_env_file(os_release
, NEWLINE
,
87 "PRETTY_NAME", &pretty_name
,
91 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
92 r
= parse_env_file(os_release
, NEWLINE
,
93 "PRETTY_NAME", &pretty_name
,
97 if (r
< 0 && r
!= -ENOENT
)
98 log_warning_errno(r
, "Failed to read os-release file: %m");
100 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
101 isempty(pretty_name
) ? "Linux" : pretty_name
);
108 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
109 unsigned n
, per_column
, i
, j
;
110 unsigned break_lines
, break_modulo
;
112 assert(n_columns
> 0);
115 per_column
= (n
+ n_columns
- 1) / n_columns
;
117 break_lines
= lines();
121 /* The first page gets two extra lines, since we want to show
123 break_modulo
= break_lines
;
124 if (break_modulo
> 3)
127 for (i
= 0; i
< per_column
; i
++) {
129 for (j
= 0; j
< n_columns
; j
++) {
130 _cleanup_free_
char *e
= NULL
;
132 if (j
* per_column
+ i
>= n
)
135 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
139 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
144 /* on the first screen we reserve 2 extra lines for the title */
145 if (i
% break_lines
== break_modulo
) {
146 if (!press_any_key())
154 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
162 _cleanup_free_
char *p
= NULL
;
165 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
167 return log_error_errno(r
, "Failed to query user: %m");
170 log_warning("No data entered, skipping.");
174 r
= safe_atou(p
, &u
);
178 if (u
<= 0 || u
> strv_length(l
)) {
179 log_error("Specified entry number out of range.");
183 log_info("Selected '%s'.", l
[u
-1]);
195 log_error("Entered data invalid.");
206 static int prompt_locale(void) {
207 _cleanup_strv_free_
char **locales
= NULL
;
210 if (arg_locale
|| arg_locale_messages
)
213 if (!arg_prompt_locale
)
216 r
= get_locales(&locales
);
218 return log_error_errno(r
, "Cannot query locales list: %m");
222 printf("\nAvailable Locales:\n\n");
223 r
= show_menu(locales
, 3, 22, 60);
229 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
233 if (isempty(arg_locale
))
236 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
243 static int process_locale(void) {
244 const char *etc_localeconf
;
249 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
250 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
253 if (arg_copy_locale
&& arg_root
) {
255 mkdir_parents(etc_localeconf
, 0755);
256 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
259 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
261 log_info("%s copied.", etc_localeconf
);
270 if (!isempty(arg_locale
))
271 locales
[i
++] = strjoina("LANG=", arg_locale
);
272 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
273 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
280 mkdir_parents(etc_localeconf
, 0755);
281 r
= write_env_file(etc_localeconf
, locales
);
283 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
285 log_info("%s written.", etc_localeconf
);
289 static int prompt_timezone(void) {
290 _cleanup_strv_free_
char **zones
= NULL
;
296 if (!arg_prompt_timezone
)
299 r
= get_timezones(&zones
);
301 return log_error_errno(r
, "Cannot query timezone list: %m");
305 printf("\nAvailable Time Zones:\n\n");
306 r
= show_menu(zones
, 3, 22, 30);
312 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
319 static int process_timezone(void) {
320 const char *etc_localtime
, *e
;
323 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
324 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
327 if (arg_copy_timezone
&& arg_root
) {
328 _cleanup_free_
char *p
= NULL
;
330 r
= readlink_malloc("/etc/localtime", &p
);
333 return log_error_errno(r
, "Failed to read host timezone: %m");
335 mkdir_parents(etc_localtime
, 0755);
336 if (symlink(p
, etc_localtime
) < 0)
337 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
339 log_info("%s copied.", etc_localtime
);
344 r
= prompt_timezone();
348 if (isempty(arg_timezone
))
351 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
353 mkdir_parents(etc_localtime
, 0755);
354 if (symlink(e
, etc_localtime
) < 0)
355 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
357 log_info("%s written", etc_localtime
);
361 static int prompt_hostname(void) {
367 if (!arg_prompt_hostname
)
374 _cleanup_free_
char *h
= NULL
;
376 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
378 return log_error_errno(r
, "Failed to query hostname: %m");
381 log_warning("No hostname entered, skipping.");
385 if (!hostname_is_valid(h
, true)) {
386 log_error("Specified hostname invalid.");
390 /* Get rid of the trailing dot that we allow, but don't want to see */
391 arg_hostname
= hostname_cleanup(h
);
399 static int process_hostname(void) {
400 const char *etc_hostname
;
403 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
404 if (faccessat(AT_FDCWD
, etc_hostname
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
407 r
= prompt_hostname();
411 if (isempty(arg_hostname
))
414 mkdir_parents(etc_hostname
, 0755);
415 r
= write_string_file(etc_hostname
, arg_hostname
, WRITE_STRING_FILE_CREATE
);
417 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
419 log_info("%s written.", etc_hostname
);
423 static int process_machine_id(void) {
424 const char *etc_machine_id
;
425 char id
[SD_ID128_STRING_MAX
];
428 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
429 if (faccessat(AT_FDCWD
, etc_machine_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
432 if (sd_id128_equal(arg_machine_id
, SD_ID128_NULL
))
435 mkdir_parents(etc_machine_id
, 0755);
436 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
), WRITE_STRING_FILE_CREATE
);
438 return log_error_errno(r
, "Failed to write machine id: %m");
440 log_info("%s written.", etc_machine_id
);
444 static int prompt_root_password(void) {
445 const char *msg1
, *msg2
, *etc_shadow
;
448 if (arg_root_password
)
451 if (!arg_prompt_root_password
)
454 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
455 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
461 msg1
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
462 msg2
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter new root password again: ");
465 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
467 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
469 return log_error_errno(r
, "Failed to query root password: %m");
472 log_warning("No password entered, skipping.");
476 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
478 return log_error_errno(r
, "Failed to query root password: %m");
481 log_error("Entered passwords did not match, please try again.");
485 arg_root_password
= a
;
493 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
494 _cleanup_fclose_
FILE *f
= NULL
;
499 f
= fopen(path
, "wex");
504 if (putspent(p
, f
) != 0)
505 return errno
> 0 ? -errno
: -EIO
;
507 return fflush_and_check(f
);
510 static int process_root_password(void) {
512 static const char table
[] =
513 "abcdefghijklmnopqrstuvwxyz"
514 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
519 .sp_namp
= (char*) "root",
525 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
528 _cleanup_close_
int lock
= -1;
534 const char *etc_shadow
;
537 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
538 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
541 mkdir_parents(etc_shadow
, 0755);
543 lock
= take_etc_passwd_lock(arg_root
);
545 return log_error_errno(lock
, "Failed to take a lock: %m");
547 if (arg_copy_root_password
&& arg_root
) {
551 p
= getspnam("root");
552 if (p
|| errno
!= ENOENT
) {
557 return log_error_errno(errno
, "Failed to find shadow entry for root: %m");
560 r
= write_root_shadow(etc_shadow
, p
);
562 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
564 log_info("%s copied.", etc_shadow
);
569 r
= prompt_root_password();
573 if (!arg_root_password
)
576 r
= dev_urandom(raw
, 16);
578 return log_error_errno(r
, "Failed to get salt: %m");
580 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
581 assert_cc(sizeof(table
) == 64 + 1);
582 j
= stpcpy(salt
, "$6$");
583 for (i
= 0; i
< 16; i
++)
584 j
[i
] = table
[raw
[i
] & 63];
589 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
594 return log_error_errno(errno
, "Failed to encrypt password: %m");
597 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
599 r
= write_root_shadow(etc_shadow
, &item
);
601 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
603 log_info("%s written.", etc_shadow
);
607 static void help(void) {
608 printf("%s [OPTIONS...]\n\n"
609 "Configures basic settings of the system.\n\n"
610 " -h --help Show this help\n"
611 " --version Show package version\n"
612 " --root=PATH Operate on an alternate filesystem root\n"
613 " --locale=LOCALE Set primary locale (LANG=)\n"
614 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
615 " --timezone=TIMEZONE Set timezone\n"
616 " --hostname=NAME Set host name\n"
617 " --machine-ID=ID Set machine ID\n"
618 " --root-password=PASSWORD Set root password\n"
619 " --root-password-file=FILE Set root password from file\n"
620 " --prompt-locale Prompt the user for locale settings\n"
621 " --prompt-timezone Prompt the user for timezone\n"
622 " --prompt-hostname Prompt the user for hostname\n"
623 " --prompt-root-password Prompt the user for root password\n"
624 " --prompt Prompt for all of the above\n"
625 " --copy-locale Copy locale from host\n"
626 " --copy-timezone Copy timezone from host\n"
627 " --copy-root-password Copy root password from host\n"
628 " --copy Copy locale, timezone, root password\n"
629 " --setup-machine-id Generate a new random machine ID\n"
630 , program_invocation_short_name
);
633 static int parse_argv(int argc
, char *argv
[]) {
644 ARG_ROOT_PASSWORD_FILE
,
649 ARG_PROMPT_ROOT_PASSWORD
,
653 ARG_COPY_ROOT_PASSWORD
,
654 ARG_SETUP_MACHINE_ID
,
657 static const struct option options
[] = {
658 { "help", no_argument
, NULL
, 'h' },
659 { "version", no_argument
, NULL
, ARG_VERSION
},
660 { "root", required_argument
, NULL
, ARG_ROOT
},
661 { "locale", required_argument
, NULL
, ARG_LOCALE
},
662 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
663 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
664 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
665 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
666 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
667 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
668 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
669 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
670 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
671 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
672 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
673 { "copy", no_argument
, NULL
, ARG_COPY
},
674 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
675 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
676 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
677 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
686 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
698 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
704 if (!locale_is_valid(optarg
)) {
705 log_error("Locale %s is not valid.", optarg
);
709 r
= free_and_strdup(&arg_locale
, optarg
);
715 case ARG_LOCALE_MESSAGES
:
716 if (!locale_is_valid(optarg
)) {
717 log_error("Locale %s is not valid.", optarg
);
721 r
= free_and_strdup(&arg_locale_messages
, optarg
);
728 if (!timezone_is_valid(optarg
)) {
729 log_error("Timezone %s is not valid.", optarg
);
733 r
= free_and_strdup(&arg_timezone
, optarg
);
739 case ARG_ROOT_PASSWORD
:
740 r
= free_and_strdup(&arg_root_password
, optarg
);
745 case ARG_ROOT_PASSWORD_FILE
:
746 arg_root_password
= mfree(arg_root_password
);
748 r
= read_one_line_file(optarg
, &arg_root_password
);
750 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
755 if (!hostname_is_valid(optarg
, true)) {
756 log_error("Host name %s is not valid.", optarg
);
760 hostname_cleanup(optarg
);
761 r
= free_and_strdup(&arg_hostname
, optarg
);
768 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
769 log_error("Failed to parse machine id %s.", optarg
);
776 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
779 case ARG_PROMPT_LOCALE
:
780 arg_prompt_locale
= true;
783 case ARG_PROMPT_TIMEZONE
:
784 arg_prompt_timezone
= true;
787 case ARG_PROMPT_HOSTNAME
:
788 arg_prompt_hostname
= true;
791 case ARG_PROMPT_ROOT_PASSWORD
:
792 arg_prompt_root_password
= true;
796 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
799 case ARG_COPY_LOCALE
:
800 arg_copy_locale
= true;
803 case ARG_COPY_TIMEZONE
:
804 arg_copy_timezone
= true;
807 case ARG_COPY_ROOT_PASSWORD
:
808 arg_copy_root_password
= true;
811 case ARG_SETUP_MACHINE_ID
:
813 r
= sd_id128_randomize(&arg_machine_id
);
815 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
823 assert_not_reached("Unhandled option");
829 int main(int argc
, char *argv
[]) {
832 r
= parse_argv(argc
, argv
);
836 log_set_target(LOG_TARGET_AUTO
);
837 log_parse_environment();
842 r
= process_locale();
846 r
= process_timezone();
850 r
= process_hostname();
854 r
= process_machine_id();
858 r
= process_root_password();
865 free(arg_locale_messages
);
868 string_erase(arg_root_password
);
869 free(arg_root_password
);
871 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;