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"
39 static char *arg_root
= NULL
;
40 static char *arg_locale
= NULL
; /* $LANG */
41 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
42 static char *arg_timezone
= NULL
;
43 static char *arg_hostname
= NULL
;
44 static sd_id128_t arg_machine_id
= {};
45 static char *arg_root_password
= NULL
;
46 static bool arg_prompt_locale
= false;
47 static bool arg_prompt_timezone
= false;
48 static bool arg_prompt_hostname
= false;
49 static bool arg_prompt_root_password
= false;
50 static bool arg_copy_locale
= false;
51 static bool arg_copy_timezone
= false;
52 static bool arg_copy_root_password
= false;
54 static void clear_string(char *x
) {
59 /* A delicious drop of snake-oil! */
60 memset(x
, 'x', strlen(x
));
63 static bool press_any_key(void) {
67 printf("-- Press any key to proceed --");
70 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
78 static void print_welcome(void) {
79 _cleanup_free_
char *pretty_name
= NULL
;
80 const char *os_release
= NULL
;
81 static bool done
= false;
87 os_release
= prefix_roota(arg_root
, "/etc/os-release");
88 r
= parse_env_file(os_release
, NEWLINE
,
89 "PRETTY_NAME", &pretty_name
,
93 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
94 r
= parse_env_file(os_release
, NEWLINE
,
95 "PRETTY_NAME", &pretty_name
,
99 if (r
< 0 && r
!= -ENOENT
)
100 log_warning_errno(r
, "Failed to read os-release file: %m");
102 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
103 isempty(pretty_name
) ? "Linux" : pretty_name
);
110 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
111 unsigned n
, per_column
, i
, j
;
112 unsigned break_lines
, break_modulo
;
114 assert(n_columns
> 0);
117 per_column
= (n
+ n_columns
- 1) / n_columns
;
119 break_lines
= lines();
123 /* The first page gets two extra lines, since we want to show
125 break_modulo
= break_lines
;
126 if (break_modulo
> 3)
129 for (i
= 0; i
< per_column
; i
++) {
131 for (j
= 0; j
< n_columns
; j
++) {
132 _cleanup_free_
char *e
= NULL
;
134 if (j
* per_column
+ i
>= n
)
137 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
141 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
146 /* on the first screen we reserve 2 extra lines for the title */
147 if (i
% break_lines
== break_modulo
) {
148 if (!press_any_key())
156 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
164 _cleanup_free_
char *p
= NULL
;
167 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
169 return log_error_errno(r
, "Failed to query user: %m");
172 log_warning("No data entered, skipping.");
176 r
= safe_atou(p
, &u
);
180 if (u
<= 0 || u
> strv_length(l
)) {
181 log_error("Specified entry number out of range.");
185 log_info("Selected '%s'.", l
[u
-1]);
197 log_error("Entered data invalid.");
208 static int prompt_locale(void) {
209 _cleanup_strv_free_
char **locales
= NULL
;
212 if (arg_locale
|| arg_locale_messages
)
215 if (!arg_prompt_locale
)
218 r
= get_locales(&locales
);
220 return log_error_errno(r
, "Cannot query locales list: %m");
224 printf("\nAvailable Locales:\n\n");
225 r
= show_menu(locales
, 3, 22, 60);
231 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
235 if (isempty(arg_locale
))
238 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
245 static int process_locale(void) {
246 const char *etc_localeconf
;
251 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
252 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
255 if (arg_copy_locale
&& arg_root
) {
257 mkdir_parents(etc_localeconf
, 0755);
258 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
261 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
263 log_info("%s copied.", etc_localeconf
);
272 if (!isempty(arg_locale
))
273 locales
[i
++] = strjoina("LANG=", arg_locale
);
274 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
275 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
282 mkdir_parents(etc_localeconf
, 0755);
283 r
= write_env_file(etc_localeconf
, locales
);
285 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
287 log_info("%s written.", etc_localeconf
);
291 static int prompt_timezone(void) {
292 _cleanup_strv_free_
char **zones
= NULL
;
298 if (!arg_prompt_timezone
)
301 r
= get_timezones(&zones
);
303 return log_error_errno(r
, "Cannot query timezone list: %m");
307 printf("\nAvailable Time Zones:\n\n");
308 r
= show_menu(zones
, 3, 22, 30);
314 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
321 static int process_timezone(void) {
322 const char *etc_localtime
, *e
;
325 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
326 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
329 if (arg_copy_timezone
&& arg_root
) {
330 _cleanup_free_
char *p
= NULL
;
332 r
= readlink_malloc("/etc/localtime", &p
);
335 return log_error_errno(r
, "Failed to read host timezone: %m");
337 mkdir_parents(etc_localtime
, 0755);
338 if (symlink(p
, etc_localtime
) < 0)
339 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
341 log_info("%s copied.", etc_localtime
);
346 r
= prompt_timezone();
350 if (isempty(arg_timezone
))
353 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
355 mkdir_parents(etc_localtime
, 0755);
356 if (symlink(e
, etc_localtime
) < 0)
357 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
359 log_info("%s written", etc_localtime
);
363 static int prompt_hostname(void) {
369 if (!arg_prompt_hostname
)
376 _cleanup_free_
char *h
= NULL
;
378 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
380 return log_error_errno(r
, "Failed to query hostname: %m");
383 log_warning("No hostname entered, skipping.");
387 if (!hostname_is_valid(h
, true)) {
388 log_error("Specified hostname invalid.");
392 /* Get rid of the trailing dot that we allow, but don't want to see */
393 arg_hostname
= hostname_cleanup(h
);
401 static int process_hostname(void) {
402 const char *etc_hostname
;
405 etc_hostname
= prefix_roota(arg_root
, "/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
, WRITE_STRING_FILE_CREATE
);
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(arg_root
, "/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
), WRITE_STRING_FILE_CREATE
);
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(arg_root
, "/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
, NULL
, 0, 0, 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
, NULL
, 0, 0, NULL
, &b
);
481 return log_error_errno(r
, "Failed to query root password: %m");
485 log_error("Entered passwords did not match, please try again.");
492 arg_root_password
= a
;
500 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
501 _cleanup_fclose_
FILE *f
= NULL
;
506 f
= fopen(path
, "wex");
511 if (putspent(p
, f
) != 0)
512 return errno
? -errno
: -EIO
;
514 return fflush_and_check(f
);
517 static int process_root_password(void) {
519 static const char table
[] =
520 "abcdefghijklmnopqrstuvwxyz"
521 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
526 .sp_namp
= (char*) "root",
532 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
535 _cleanup_close_
int lock
= -1;
541 const char *etc_shadow
;
544 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
545 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
548 mkdir_parents(etc_shadow
, 0755);
550 lock
= take_password_lock(arg_root
);
554 if (arg_copy_root_password
&& arg_root
) {
558 p
= getspnam("root");
559 if (p
|| errno
!= ENOENT
) {
564 log_error_errno(errno
, "Failed to find shadow entry for root: %m");
568 r
= write_root_shadow(etc_shadow
, p
);
570 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
572 log_info("%s copied.", etc_shadow
);
577 r
= prompt_root_password();
581 if (!arg_root_password
)
584 r
= dev_urandom(raw
, 16);
586 return log_error_errno(r
, "Failed to get salt: %m");
588 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
589 assert_cc(sizeof(table
) == 64 + 1);
590 j
= stpcpy(salt
, "$6$");
591 for (i
= 0; i
< 16; i
++)
592 j
[i
] = table
[raw
[i
] & 63];
597 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
602 log_error_errno(errno
, "Failed to encrypt password: %m");
606 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
608 r
= write_root_shadow(etc_shadow
, &item
);
610 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
612 log_info("%s written.", etc_shadow
);
616 static void help(void) {
617 printf("%s [OPTIONS...]\n\n"
618 "Configures basic settings of the system.\n\n"
619 " -h --help Show this help\n"
620 " --version Show package version\n"
621 " --root=PATH Operate on an alternate filesystem root\n"
622 " --locale=LOCALE Set primary locale (LANG=)\n"
623 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
624 " --timezone=TIMEZONE Set timezone\n"
625 " --hostname=NAME Set host name\n"
626 " --machine-ID=ID Set machine ID\n"
627 " --root-password=PASSWORD Set root password\n"
628 " --root-password-file=FILE Set root password from file\n"
629 " --prompt-locale Prompt the user for locale settings\n"
630 " --prompt-timezone Prompt the user for timezone\n"
631 " --prompt-hostname Prompt the user for hostname\n"
632 " --prompt-root-password Prompt the user for root password\n"
633 " --prompt Prompt for all of the above\n"
634 " --copy-locale Copy locale from host\n"
635 " --copy-timezone Copy timezone from host\n"
636 " --copy-root-password Copy root password from host\n"
637 " --copy Copy locale, timezone, root password\n"
638 " --setup-machine-id Generate a new random machine ID\n"
639 , program_invocation_short_name
);
642 static int parse_argv(int argc
, char *argv
[]) {
653 ARG_ROOT_PASSWORD_FILE
,
658 ARG_PROMPT_ROOT_PASSWORD
,
662 ARG_COPY_ROOT_PASSWORD
,
663 ARG_SETUP_MACHINE_ID
,
666 static const struct option options
[] = {
667 { "help", no_argument
, NULL
, 'h' },
668 { "version", no_argument
, NULL
, ARG_VERSION
},
669 { "root", required_argument
, NULL
, ARG_ROOT
},
670 { "locale", required_argument
, NULL
, ARG_LOCALE
},
671 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
672 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
673 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
674 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
675 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
676 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
677 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
678 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
679 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
680 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
681 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
682 { "copy", no_argument
, NULL
, ARG_COPY
},
683 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
684 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
685 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
686 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
695 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
708 arg_root
= path_make_absolute_cwd(optarg
);
712 path_kill_slashes(arg_root
);
714 if (path_equal(arg_root
, "/"))
715 arg_root
= mfree(arg_root
);
720 if (!locale_is_valid(optarg
)) {
721 log_error("Locale %s is not valid.", optarg
);
725 r
= free_and_strdup(&arg_locale
, optarg
);
731 case ARG_LOCALE_MESSAGES
:
732 if (!locale_is_valid(optarg
)) {
733 log_error("Locale %s is not valid.", optarg
);
737 r
= free_and_strdup(&arg_locale_messages
, optarg
);
744 if (!timezone_is_valid(optarg
)) {
745 log_error("Timezone %s is not valid.", optarg
);
749 r
= free_and_strdup(&arg_timezone
, optarg
);
755 case ARG_ROOT_PASSWORD
:
756 r
= free_and_strdup(&arg_root_password
, optarg
);
761 case ARG_ROOT_PASSWORD_FILE
:
762 arg_root_password
= mfree(arg_root_password
);
764 r
= read_one_line_file(optarg
, &arg_root_password
);
766 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
771 if (!hostname_is_valid(optarg
, true)) {
772 log_error("Host name %s is not valid.", optarg
);
776 hostname_cleanup(optarg
);
777 r
= free_and_strdup(&arg_hostname
, optarg
);
784 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
785 log_error("Failed to parse machine id %s.", optarg
);
792 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
795 case ARG_PROMPT_LOCALE
:
796 arg_prompt_locale
= true;
799 case ARG_PROMPT_TIMEZONE
:
800 arg_prompt_timezone
= true;
803 case ARG_PROMPT_HOSTNAME
:
804 arg_prompt_hostname
= true;
807 case ARG_PROMPT_ROOT_PASSWORD
:
808 arg_prompt_root_password
= true;
812 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
815 case ARG_COPY_LOCALE
:
816 arg_copy_locale
= true;
819 case ARG_COPY_TIMEZONE
:
820 arg_copy_timezone
= true;
823 case ARG_COPY_ROOT_PASSWORD
:
824 arg_copy_root_password
= true;
827 case ARG_SETUP_MACHINE_ID
:
829 r
= sd_id128_randomize(&arg_machine_id
);
831 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
839 assert_not_reached("Unhandled option");
845 int main(int argc
, char *argv
[]) {
848 r
= parse_argv(argc
, argv
);
852 log_set_target(LOG_TARGET_AUTO
);
853 log_parse_environment();
858 r
= process_locale();
862 r
= process_timezone();
866 r
= process_hostname();
870 r
= process_machine_id();
874 r
= process_root_password();
881 free(arg_locale_messages
);
884 clear_string(arg_root_password
);
885 free(arg_root_password
);
887 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;