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"
31 #include "hostname-util.h"
32 #include "locale-util.h"
34 #include "parse-util.h"
35 #include "path-util.h"
36 #include "random-util.h"
37 #include "string-util.h"
39 #include "terminal-util.h"
40 #include "time-util.h"
41 #include "user-util.h"
43 static char *arg_root
= NULL
;
44 static char *arg_locale
= NULL
; /* $LANG */
45 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
46 static char *arg_timezone
= NULL
;
47 static char *arg_hostname
= NULL
;
48 static sd_id128_t arg_machine_id
= {};
49 static char *arg_root_password
= NULL
;
50 static bool arg_prompt_locale
= false;
51 static bool arg_prompt_timezone
= false;
52 static bool arg_prompt_hostname
= false;
53 static bool arg_prompt_root_password
= false;
54 static bool arg_copy_locale
= false;
55 static bool arg_copy_timezone
= false;
56 static bool arg_copy_root_password
= false;
58 static bool press_any_key(void) {
62 printf("-- Press any key to proceed --");
65 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
73 static void print_welcome(void) {
74 _cleanup_free_
char *pretty_name
= NULL
;
75 const char *os_release
= NULL
;
76 static bool done
= false;
82 os_release
= prefix_roota(arg_root
, "/etc/os-release");
83 r
= parse_env_file(os_release
, NEWLINE
,
84 "PRETTY_NAME", &pretty_name
,
88 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
89 r
= parse_env_file(os_release
, NEWLINE
,
90 "PRETTY_NAME", &pretty_name
,
94 if (r
< 0 && r
!= -ENOENT
)
95 log_warning_errno(r
, "Failed to read os-release file: %m");
97 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
98 isempty(pretty_name
) ? "Linux" : pretty_name
);
105 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
106 unsigned n
, per_column
, i
, j
;
107 unsigned break_lines
, break_modulo
;
109 assert(n_columns
> 0);
112 per_column
= (n
+ n_columns
- 1) / n_columns
;
114 break_lines
= lines();
118 /* The first page gets two extra lines, since we want to show
120 break_modulo
= break_lines
;
121 if (break_modulo
> 3)
124 for (i
= 0; i
< per_column
; i
++) {
126 for (j
= 0; j
< n_columns
; j
++) {
127 _cleanup_free_
char *e
= NULL
;
129 if (j
* per_column
+ i
>= n
)
132 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
136 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
141 /* on the first screen we reserve 2 extra lines for the title */
142 if (i
% break_lines
== break_modulo
) {
143 if (!press_any_key())
151 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
159 _cleanup_free_
char *p
= NULL
;
162 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
164 return log_error_errno(r
, "Failed to query user: %m");
167 log_warning("No data entered, skipping.");
171 r
= safe_atou(p
, &u
);
175 if (u
<= 0 || u
> strv_length(l
)) {
176 log_error("Specified entry number out of range.");
180 log_info("Selected '%s'.", l
[u
-1]);
192 log_error("Entered data invalid.");
203 static int prompt_locale(void) {
204 _cleanup_strv_free_
char **locales
= NULL
;
207 if (arg_locale
|| arg_locale_messages
)
210 if (!arg_prompt_locale
)
213 r
= get_locales(&locales
);
215 return log_error_errno(r
, "Cannot query locales list: %m");
219 printf("\nAvailable Locales:\n\n");
220 r
= show_menu(locales
, 3, 22, 60);
226 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
230 if (isempty(arg_locale
))
233 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
240 static int process_locale(void) {
241 const char *etc_localeconf
;
246 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
247 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
250 if (arg_copy_locale
&& arg_root
) {
252 mkdir_parents(etc_localeconf
, 0755);
253 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
256 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
258 log_info("%s copied.", etc_localeconf
);
267 if (!isempty(arg_locale
))
268 locales
[i
++] = strjoina("LANG=", arg_locale
);
269 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
270 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
277 mkdir_parents(etc_localeconf
, 0755);
278 r
= write_env_file(etc_localeconf
, locales
);
280 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
282 log_info("%s written.", etc_localeconf
);
286 static int prompt_timezone(void) {
287 _cleanup_strv_free_
char **zones
= NULL
;
293 if (!arg_prompt_timezone
)
296 r
= get_timezones(&zones
);
298 return log_error_errno(r
, "Cannot query timezone list: %m");
302 printf("\nAvailable Time Zones:\n\n");
303 r
= show_menu(zones
, 3, 22, 30);
309 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
316 static int process_timezone(void) {
317 const char *etc_localtime
, *e
;
320 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
321 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
324 if (arg_copy_timezone
&& arg_root
) {
325 _cleanup_free_
char *p
= NULL
;
327 r
= readlink_malloc("/etc/localtime", &p
);
330 return log_error_errno(r
, "Failed to read host timezone: %m");
332 mkdir_parents(etc_localtime
, 0755);
333 if (symlink(p
, etc_localtime
) < 0)
334 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
336 log_info("%s copied.", etc_localtime
);
341 r
= prompt_timezone();
345 if (isempty(arg_timezone
))
348 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
350 mkdir_parents(etc_localtime
, 0755);
351 if (symlink(e
, etc_localtime
) < 0)
352 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
354 log_info("%s written", etc_localtime
);
358 static int prompt_hostname(void) {
364 if (!arg_prompt_hostname
)
371 _cleanup_free_
char *h
= NULL
;
373 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
375 return log_error_errno(r
, "Failed to query hostname: %m");
378 log_warning("No hostname entered, skipping.");
382 if (!hostname_is_valid(h
, true)) {
383 log_error("Specified hostname invalid.");
387 /* Get rid of the trailing dot that we allow, but don't want to see */
388 arg_hostname
= hostname_cleanup(h
);
396 static int process_hostname(void) {
397 const char *etc_hostname
;
400 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
401 if (faccessat(AT_FDCWD
, etc_hostname
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
404 r
= prompt_hostname();
408 if (isempty(arg_hostname
))
411 mkdir_parents(etc_hostname
, 0755);
412 r
= write_string_file(etc_hostname
, arg_hostname
, WRITE_STRING_FILE_CREATE
);
414 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
416 log_info("%s written.", etc_hostname
);
420 static int process_machine_id(void) {
421 const char *etc_machine_id
;
422 char id
[SD_ID128_STRING_MAX
];
425 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
426 if (faccessat(AT_FDCWD
, etc_machine_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
429 if (sd_id128_equal(arg_machine_id
, SD_ID128_NULL
))
432 mkdir_parents(etc_machine_id
, 0755);
433 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
), WRITE_STRING_FILE_CREATE
);
435 return log_error_errno(r
, "Failed to write machine id: %m");
437 log_info("%s written.", etc_machine_id
);
441 static int prompt_root_password(void) {
442 const char *msg1
, *msg2
, *etc_shadow
;
445 if (arg_root_password
)
448 if (!arg_prompt_root_password
)
451 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
452 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
458 msg1
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
459 msg2
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter new root password again: ");
462 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
464 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
466 return log_error_errno(r
, "Failed to query root password: %m");
469 log_warning("No password entered, skipping.");
473 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
475 return log_error_errno(r
, "Failed to query root password: %m");
478 log_error("Entered passwords did not match, please try again.");
482 arg_root_password
= a
;
490 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
491 _cleanup_fclose_
FILE *f
= NULL
;
496 f
= fopen(path
, "wex");
501 if (putspent(p
, f
) != 0)
502 return errno
? -errno
: -EIO
;
504 return fflush_and_check(f
);
507 static int process_root_password(void) {
509 static const char table
[] =
510 "abcdefghijklmnopqrstuvwxyz"
511 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
516 .sp_namp
= (char*) "root",
522 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
525 _cleanup_close_
int lock
= -1;
531 const char *etc_shadow
;
534 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
535 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
538 mkdir_parents(etc_shadow
, 0755);
540 lock
= take_etc_passwd_lock(arg_root
);
544 if (arg_copy_root_password
&& arg_root
) {
548 p
= getspnam("root");
549 if (p
|| errno
!= ENOENT
) {
554 log_error_errno(errno
, "Failed to find shadow entry for root: %m");
558 r
= write_root_shadow(etc_shadow
, p
);
560 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
562 log_info("%s copied.", etc_shadow
);
567 r
= prompt_root_password();
571 if (!arg_root_password
)
574 r
= dev_urandom(raw
, 16);
576 return log_error_errno(r
, "Failed to get salt: %m");
578 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
579 assert_cc(sizeof(table
) == 64 + 1);
580 j
= stpcpy(salt
, "$6$");
581 for (i
= 0; i
< 16; i
++)
582 j
[i
] = table
[raw
[i
] & 63];
587 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
592 log_error_errno(errno
, "Failed to encrypt password: %m");
596 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
598 r
= write_root_shadow(etc_shadow
, &item
);
600 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
602 log_info("%s written.", etc_shadow
);
606 static void help(void) {
607 printf("%s [OPTIONS...]\n\n"
608 "Configures basic settings of the system.\n\n"
609 " -h --help Show this help\n"
610 " --version Show package version\n"
611 " --root=PATH Operate on an alternate filesystem root\n"
612 " --locale=LOCALE Set primary locale (LANG=)\n"
613 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
614 " --timezone=TIMEZONE Set timezone\n"
615 " --hostname=NAME Set host name\n"
616 " --machine-ID=ID Set machine ID\n"
617 " --root-password=PASSWORD Set root password\n"
618 " --root-password-file=FILE Set root password from file\n"
619 " --prompt-locale Prompt the user for locale settings\n"
620 " --prompt-timezone Prompt the user for timezone\n"
621 " --prompt-hostname Prompt the user for hostname\n"
622 " --prompt-root-password Prompt the user for root password\n"
623 " --prompt Prompt for all of the above\n"
624 " --copy-locale Copy locale from host\n"
625 " --copy-timezone Copy timezone from host\n"
626 " --copy-root-password Copy root password from host\n"
627 " --copy Copy locale, timezone, root password\n"
628 " --setup-machine-id Generate a new random machine ID\n"
629 , program_invocation_short_name
);
632 static int parse_argv(int argc
, char *argv
[]) {
643 ARG_ROOT_PASSWORD_FILE
,
648 ARG_PROMPT_ROOT_PASSWORD
,
652 ARG_COPY_ROOT_PASSWORD
,
653 ARG_SETUP_MACHINE_ID
,
656 static const struct option options
[] = {
657 { "help", no_argument
, NULL
, 'h' },
658 { "version", no_argument
, NULL
, ARG_VERSION
},
659 { "root", required_argument
, NULL
, ARG_ROOT
},
660 { "locale", required_argument
, NULL
, ARG_LOCALE
},
661 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
662 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
663 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
664 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
665 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
666 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
667 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
668 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
669 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
670 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
671 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
672 { "copy", no_argument
, NULL
, ARG_COPY
},
673 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
674 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
675 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
676 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
685 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
697 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
703 if (!locale_is_valid(optarg
)) {
704 log_error("Locale %s is not valid.", optarg
);
708 r
= free_and_strdup(&arg_locale
, optarg
);
714 case ARG_LOCALE_MESSAGES
:
715 if (!locale_is_valid(optarg
)) {
716 log_error("Locale %s is not valid.", optarg
);
720 r
= free_and_strdup(&arg_locale_messages
, optarg
);
727 if (!timezone_is_valid(optarg
)) {
728 log_error("Timezone %s is not valid.", optarg
);
732 r
= free_and_strdup(&arg_timezone
, optarg
);
738 case ARG_ROOT_PASSWORD
:
739 r
= free_and_strdup(&arg_root_password
, optarg
);
744 case ARG_ROOT_PASSWORD_FILE
:
745 arg_root_password
= mfree(arg_root_password
);
747 r
= read_one_line_file(optarg
, &arg_root_password
);
749 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
754 if (!hostname_is_valid(optarg
, true)) {
755 log_error("Host name %s is not valid.", optarg
);
759 hostname_cleanup(optarg
);
760 r
= free_and_strdup(&arg_hostname
, optarg
);
767 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
768 log_error("Failed to parse machine id %s.", optarg
);
775 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
778 case ARG_PROMPT_LOCALE
:
779 arg_prompt_locale
= true;
782 case ARG_PROMPT_TIMEZONE
:
783 arg_prompt_timezone
= true;
786 case ARG_PROMPT_HOSTNAME
:
787 arg_prompt_hostname
= true;
790 case ARG_PROMPT_ROOT_PASSWORD
:
791 arg_prompt_root_password
= true;
795 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
798 case ARG_COPY_LOCALE
:
799 arg_copy_locale
= true;
802 case ARG_COPY_TIMEZONE
:
803 arg_copy_timezone
= true;
806 case ARG_COPY_ROOT_PASSWORD
:
807 arg_copy_root_password
= true;
810 case ARG_SETUP_MACHINE_ID
:
812 r
= sd_id128_randomize(&arg_machine_id
);
814 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
822 assert_not_reached("Unhandled option");
828 int main(int argc
, char *argv
[]) {
831 r
= parse_argv(argc
, argv
);
835 log_set_target(LOG_TARGET_AUTO
);
836 log_parse_environment();
841 r
= process_locale();
845 r
= process_timezone();
849 r
= process_hostname();
853 r
= process_machine_id();
857 r
= process_root_password();
864 free(arg_locale_messages
);
867 string_erase(arg_root_password
);
868 free(arg_root_password
);
870 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;