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 "ask-password-api.h"
30 #include "hostname-util.h"
31 #include "locale-util.h"
33 #include "path-util.h"
34 #include "random-util.h"
36 #include "terminal-util.h"
37 #include "time-util.h"
38 #include "string-util.h"
40 static char *arg_root
= NULL
;
41 static char *arg_locale
= NULL
; /* $LANG */
42 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
43 static char *arg_timezone
= NULL
;
44 static char *arg_hostname
= NULL
;
45 static sd_id128_t arg_machine_id
= {};
46 static char *arg_root_password
= NULL
;
47 static bool arg_prompt_locale
= false;
48 static bool arg_prompt_timezone
= false;
49 static bool arg_prompt_hostname
= false;
50 static bool arg_prompt_root_password
= false;
51 static bool arg_copy_locale
= false;
52 static bool arg_copy_timezone
= false;
53 static bool arg_copy_root_password
= false;
55 static bool press_any_key(void) {
59 printf("-- Press any key to proceed --");
62 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
70 static void print_welcome(void) {
71 _cleanup_free_
char *pretty_name
= NULL
;
72 const char *os_release
= NULL
;
73 static bool done
= false;
79 os_release
= prefix_roota(arg_root
, "/etc/os-release");
80 r
= parse_env_file(os_release
, NEWLINE
,
81 "PRETTY_NAME", &pretty_name
,
85 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
86 r
= parse_env_file(os_release
, NEWLINE
,
87 "PRETTY_NAME", &pretty_name
,
91 if (r
< 0 && r
!= -ENOENT
)
92 log_warning_errno(r
, "Failed to read os-release file: %m");
94 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
95 isempty(pretty_name
) ? "Linux" : pretty_name
);
102 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
103 unsigned n
, per_column
, i
, j
;
104 unsigned break_lines
, break_modulo
;
106 assert(n_columns
> 0);
109 per_column
= (n
+ n_columns
- 1) / n_columns
;
111 break_lines
= lines();
115 /* The first page gets two extra lines, since we want to show
117 break_modulo
= break_lines
;
118 if (break_modulo
> 3)
121 for (i
= 0; i
< per_column
; i
++) {
123 for (j
= 0; j
< n_columns
; j
++) {
124 _cleanup_free_
char *e
= NULL
;
126 if (j
* per_column
+ i
>= n
)
129 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
133 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
138 /* on the first screen we reserve 2 extra lines for the title */
139 if (i
% break_lines
== break_modulo
) {
140 if (!press_any_key())
148 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
156 _cleanup_free_
char *p
= NULL
;
159 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
161 return log_error_errno(r
, "Failed to query user: %m");
164 log_warning("No data entered, skipping.");
168 r
= safe_atou(p
, &u
);
172 if (u
<= 0 || u
> strv_length(l
)) {
173 log_error("Specified entry number out of range.");
177 log_info("Selected '%s'.", l
[u
-1]);
189 log_error("Entered data invalid.");
200 static int prompt_locale(void) {
201 _cleanup_strv_free_
char **locales
= NULL
;
204 if (arg_locale
|| arg_locale_messages
)
207 if (!arg_prompt_locale
)
210 r
= get_locales(&locales
);
212 return log_error_errno(r
, "Cannot query locales list: %m");
216 printf("\nAvailable Locales:\n\n");
217 r
= show_menu(locales
, 3, 22, 60);
223 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
227 if (isempty(arg_locale
))
230 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
237 static int process_locale(void) {
238 const char *etc_localeconf
;
243 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
244 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
247 if (arg_copy_locale
&& arg_root
) {
249 mkdir_parents(etc_localeconf
, 0755);
250 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
253 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
255 log_info("%s copied.", etc_localeconf
);
264 if (!isempty(arg_locale
))
265 locales
[i
++] = strjoina("LANG=", arg_locale
);
266 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
267 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
274 mkdir_parents(etc_localeconf
, 0755);
275 r
= write_env_file(etc_localeconf
, locales
);
277 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
279 log_info("%s written.", etc_localeconf
);
283 static int prompt_timezone(void) {
284 _cleanup_strv_free_
char **zones
= NULL
;
290 if (!arg_prompt_timezone
)
293 r
= get_timezones(&zones
);
295 return log_error_errno(r
, "Cannot query timezone list: %m");
299 printf("\nAvailable Time Zones:\n\n");
300 r
= show_menu(zones
, 3, 22, 30);
306 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
313 static int process_timezone(void) {
314 const char *etc_localtime
, *e
;
317 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
318 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
321 if (arg_copy_timezone
&& arg_root
) {
322 _cleanup_free_
char *p
= NULL
;
324 r
= readlink_malloc("/etc/localtime", &p
);
327 return log_error_errno(r
, "Failed to read host timezone: %m");
329 mkdir_parents(etc_localtime
, 0755);
330 if (symlink(p
, etc_localtime
) < 0)
331 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
333 log_info("%s copied.", etc_localtime
);
338 r
= prompt_timezone();
342 if (isempty(arg_timezone
))
345 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
347 mkdir_parents(etc_localtime
, 0755);
348 if (symlink(e
, etc_localtime
) < 0)
349 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
351 log_info("%s written", etc_localtime
);
355 static int prompt_hostname(void) {
361 if (!arg_prompt_hostname
)
368 _cleanup_free_
char *h
= NULL
;
370 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
372 return log_error_errno(r
, "Failed to query hostname: %m");
375 log_warning("No hostname entered, skipping.");
379 if (!hostname_is_valid(h
, true)) {
380 log_error("Specified hostname invalid.");
384 /* Get rid of the trailing dot that we allow, but don't want to see */
385 arg_hostname
= hostname_cleanup(h
);
393 static int process_hostname(void) {
394 const char *etc_hostname
;
397 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
398 if (faccessat(AT_FDCWD
, etc_hostname
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
401 r
= prompt_hostname();
405 if (isempty(arg_hostname
))
408 mkdir_parents(etc_hostname
, 0755);
409 r
= write_string_file(etc_hostname
, arg_hostname
, WRITE_STRING_FILE_CREATE
);
411 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
413 log_info("%s written.", etc_hostname
);
417 static int process_machine_id(void) {
418 const char *etc_machine_id
;
419 char id
[SD_ID128_STRING_MAX
];
422 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
423 if (faccessat(AT_FDCWD
, etc_machine_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
426 if (sd_id128_equal(arg_machine_id
, SD_ID128_NULL
))
429 mkdir_parents(etc_machine_id
, 0755);
430 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
), WRITE_STRING_FILE_CREATE
);
432 return log_error_errno(r
, "Failed to write machine id: %m");
434 log_info("%s written.", etc_machine_id
);
438 static int prompt_root_password(void) {
439 const char *msg1
, *msg2
, *etc_shadow
;
442 if (arg_root_password
)
445 if (!arg_prompt_root_password
)
448 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
449 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
455 msg1
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
456 msg2
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter new root password again: ");
459 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
461 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
463 return log_error_errno(r
, "Failed to query root password: %m");
466 log_warning("No password entered, skipping.");
470 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
472 return log_error_errno(r
, "Failed to query root password: %m");
475 log_error("Entered passwords did not match, please try again.");
479 arg_root_password
= a
;
487 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
488 _cleanup_fclose_
FILE *f
= NULL
;
493 f
= fopen(path
, "wex");
498 if (putspent(p
, f
) != 0)
499 return errno
? -errno
: -EIO
;
501 return fflush_and_check(f
);
504 static int process_root_password(void) {
506 static const char table
[] =
507 "abcdefghijklmnopqrstuvwxyz"
508 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
513 .sp_namp
= (char*) "root",
519 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
522 _cleanup_close_
int lock
= -1;
528 const char *etc_shadow
;
531 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
532 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
535 mkdir_parents(etc_shadow
, 0755);
537 lock
= take_password_lock(arg_root
);
541 if (arg_copy_root_password
&& arg_root
) {
545 p
= getspnam("root");
546 if (p
|| errno
!= ENOENT
) {
551 log_error_errno(errno
, "Failed to find shadow entry for root: %m");
555 r
= write_root_shadow(etc_shadow
, p
);
557 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
559 log_info("%s copied.", etc_shadow
);
564 r
= prompt_root_password();
568 if (!arg_root_password
)
571 r
= dev_urandom(raw
, 16);
573 return log_error_errno(r
, "Failed to get salt: %m");
575 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
576 assert_cc(sizeof(table
) == 64 + 1);
577 j
= stpcpy(salt
, "$6$");
578 for (i
= 0; i
< 16; i
++)
579 j
[i
] = table
[raw
[i
] & 63];
584 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
589 log_error_errno(errno
, "Failed to encrypt password: %m");
593 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
595 r
= write_root_shadow(etc_shadow
, &item
);
597 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
599 log_info("%s written.", etc_shadow
);
603 static void help(void) {
604 printf("%s [OPTIONS...]\n\n"
605 "Configures basic settings of the system.\n\n"
606 " -h --help Show this help\n"
607 " --version Show package version\n"
608 " --root=PATH Operate on an alternate filesystem root\n"
609 " --locale=LOCALE Set primary locale (LANG=)\n"
610 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
611 " --timezone=TIMEZONE Set timezone\n"
612 " --hostname=NAME Set host name\n"
613 " --machine-ID=ID Set machine ID\n"
614 " --root-password=PASSWORD Set root password\n"
615 " --root-password-file=FILE Set root password from file\n"
616 " --prompt-locale Prompt the user for locale settings\n"
617 " --prompt-timezone Prompt the user for timezone\n"
618 " --prompt-hostname Prompt the user for hostname\n"
619 " --prompt-root-password Prompt the user for root password\n"
620 " --prompt Prompt for all of the above\n"
621 " --copy-locale Copy locale from host\n"
622 " --copy-timezone Copy timezone from host\n"
623 " --copy-root-password Copy root password from host\n"
624 " --copy Copy locale, timezone, root password\n"
625 " --setup-machine-id Generate a new random machine ID\n"
626 , program_invocation_short_name
);
629 static int parse_argv(int argc
, char *argv
[]) {
640 ARG_ROOT_PASSWORD_FILE
,
645 ARG_PROMPT_ROOT_PASSWORD
,
649 ARG_COPY_ROOT_PASSWORD
,
650 ARG_SETUP_MACHINE_ID
,
653 static const struct option options
[] = {
654 { "help", no_argument
, NULL
, 'h' },
655 { "version", no_argument
, NULL
, ARG_VERSION
},
656 { "root", required_argument
, NULL
, ARG_ROOT
},
657 { "locale", required_argument
, NULL
, ARG_LOCALE
},
658 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
659 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
660 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
661 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
662 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
663 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
664 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
665 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
666 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
667 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
668 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
669 { "copy", no_argument
, NULL
, ARG_COPY
},
670 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
671 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
672 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
673 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
682 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
694 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
700 if (!locale_is_valid(optarg
)) {
701 log_error("Locale %s is not valid.", optarg
);
705 r
= free_and_strdup(&arg_locale
, optarg
);
711 case ARG_LOCALE_MESSAGES
:
712 if (!locale_is_valid(optarg
)) {
713 log_error("Locale %s is not valid.", optarg
);
717 r
= free_and_strdup(&arg_locale_messages
, optarg
);
724 if (!timezone_is_valid(optarg
)) {
725 log_error("Timezone %s is not valid.", optarg
);
729 r
= free_and_strdup(&arg_timezone
, optarg
);
735 case ARG_ROOT_PASSWORD
:
736 r
= free_and_strdup(&arg_root_password
, optarg
);
741 case ARG_ROOT_PASSWORD_FILE
:
742 arg_root_password
= mfree(arg_root_password
);
744 r
= read_one_line_file(optarg
, &arg_root_password
);
746 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
751 if (!hostname_is_valid(optarg
, true)) {
752 log_error("Host name %s is not valid.", optarg
);
756 hostname_cleanup(optarg
);
757 r
= free_and_strdup(&arg_hostname
, optarg
);
764 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
765 log_error("Failed to parse machine id %s.", optarg
);
772 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
775 case ARG_PROMPT_LOCALE
:
776 arg_prompt_locale
= true;
779 case ARG_PROMPT_TIMEZONE
:
780 arg_prompt_timezone
= true;
783 case ARG_PROMPT_HOSTNAME
:
784 arg_prompt_hostname
= true;
787 case ARG_PROMPT_ROOT_PASSWORD
:
788 arg_prompt_root_password
= true;
792 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
795 case ARG_COPY_LOCALE
:
796 arg_copy_locale
= true;
799 case ARG_COPY_TIMEZONE
:
800 arg_copy_timezone
= true;
803 case ARG_COPY_ROOT_PASSWORD
:
804 arg_copy_root_password
= true;
807 case ARG_SETUP_MACHINE_ID
:
809 r
= sd_id128_randomize(&arg_machine_id
);
811 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
819 assert_not_reached("Unhandled option");
825 int main(int argc
, char *argv
[]) {
828 r
= parse_argv(argc
, argv
);
832 log_set_target(LOG_TARGET_AUTO
);
833 log_parse_environment();
838 r
= process_locale();
842 r
= process_timezone();
846 r
= process_hostname();
850 r
= process_machine_id();
854 r
= process_root_password();
861 free(arg_locale_messages
);
864 string_erase(arg_root_password
);
865 free(arg_root_password
);
867 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;