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 bool press_any_key(void) {
58 printf("-- Press any key to proceed --");
61 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
69 static void print_welcome(void) {
70 _cleanup_free_
char *pretty_name
= NULL
;
71 const char *os_release
= NULL
;
72 static bool done
= false;
78 os_release
= prefix_roota(arg_root
, "/etc/os-release");
79 r
= parse_env_file(os_release
, NEWLINE
,
80 "PRETTY_NAME", &pretty_name
,
84 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
85 r
= parse_env_file(os_release
, NEWLINE
,
86 "PRETTY_NAME", &pretty_name
,
90 if (r
< 0 && r
!= -ENOENT
)
91 log_warning_errno(r
, "Failed to read os-release file: %m");
93 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
94 isempty(pretty_name
) ? "Linux" : pretty_name
);
101 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
102 unsigned n
, per_column
, i
, j
;
103 unsigned break_lines
, break_modulo
;
105 assert(n_columns
> 0);
108 per_column
= (n
+ n_columns
- 1) / n_columns
;
110 break_lines
= lines();
114 /* The first page gets two extra lines, since we want to show
116 break_modulo
= break_lines
;
117 if (break_modulo
> 3)
120 for (i
= 0; i
< per_column
; i
++) {
122 for (j
= 0; j
< n_columns
; j
++) {
123 _cleanup_free_
char *e
= NULL
;
125 if (j
* per_column
+ i
>= n
)
128 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
132 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
137 /* on the first screen we reserve 2 extra lines for the title */
138 if (i
% break_lines
== break_modulo
) {
139 if (!press_any_key())
147 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
155 _cleanup_free_
char *p
= NULL
;
158 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
160 return log_error_errno(r
, "Failed to query user: %m");
163 log_warning("No data entered, skipping.");
167 r
= safe_atou(p
, &u
);
171 if (u
<= 0 || u
> strv_length(l
)) {
172 log_error("Specified entry number out of range.");
176 log_info("Selected '%s'.", l
[u
-1]);
188 log_error("Entered data invalid.");
199 static int prompt_locale(void) {
200 _cleanup_strv_free_
char **locales
= NULL
;
203 if (arg_locale
|| arg_locale_messages
)
206 if (!arg_prompt_locale
)
209 r
= get_locales(&locales
);
211 return log_error_errno(r
, "Cannot query locales list: %m");
215 printf("\nAvailable Locales:\n\n");
216 r
= show_menu(locales
, 3, 22, 60);
222 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
226 if (isempty(arg_locale
))
229 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
236 static int process_locale(void) {
237 const char *etc_localeconf
;
242 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
243 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
246 if (arg_copy_locale
&& arg_root
) {
248 mkdir_parents(etc_localeconf
, 0755);
249 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
252 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
254 log_info("%s copied.", etc_localeconf
);
263 if (!isempty(arg_locale
))
264 locales
[i
++] = strjoina("LANG=", arg_locale
);
265 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
266 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
273 mkdir_parents(etc_localeconf
, 0755);
274 r
= write_env_file(etc_localeconf
, locales
);
276 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
278 log_info("%s written.", etc_localeconf
);
282 static int prompt_timezone(void) {
283 _cleanup_strv_free_
char **zones
= NULL
;
289 if (!arg_prompt_timezone
)
292 r
= get_timezones(&zones
);
294 return log_error_errno(r
, "Cannot query timezone list: %m");
298 printf("\nAvailable Time Zones:\n\n");
299 r
= show_menu(zones
, 3, 22, 30);
305 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
312 static int process_timezone(void) {
313 const char *etc_localtime
, *e
;
316 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
317 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
320 if (arg_copy_timezone
&& arg_root
) {
321 _cleanup_free_
char *p
= NULL
;
323 r
= readlink_malloc("/etc/localtime", &p
);
326 return log_error_errno(r
, "Failed to read host timezone: %m");
328 mkdir_parents(etc_localtime
, 0755);
329 if (symlink(p
, etc_localtime
) < 0)
330 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
332 log_info("%s copied.", etc_localtime
);
337 r
= prompt_timezone();
341 if (isempty(arg_timezone
))
344 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
346 mkdir_parents(etc_localtime
, 0755);
347 if (symlink(e
, etc_localtime
) < 0)
348 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
350 log_info("%s written", etc_localtime
);
354 static int prompt_hostname(void) {
360 if (!arg_prompt_hostname
)
367 _cleanup_free_
char *h
= NULL
;
369 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
371 return log_error_errno(r
, "Failed to query hostname: %m");
374 log_warning("No hostname entered, skipping.");
378 if (!hostname_is_valid(h
, true)) {
379 log_error("Specified hostname invalid.");
383 /* Get rid of the trailing dot that we allow, but don't want to see */
384 arg_hostname
= hostname_cleanup(h
);
392 static int process_hostname(void) {
393 const char *etc_hostname
;
396 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
397 if (faccessat(AT_FDCWD
, etc_hostname
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
400 r
= prompt_hostname();
404 if (isempty(arg_hostname
))
407 mkdir_parents(etc_hostname
, 0755);
408 r
= write_string_file(etc_hostname
, arg_hostname
, WRITE_STRING_FILE_CREATE
);
410 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
412 log_info("%s written.", etc_hostname
);
416 static int process_machine_id(void) {
417 const char *etc_machine_id
;
418 char id
[SD_ID128_STRING_MAX
];
421 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
422 if (faccessat(AT_FDCWD
, etc_machine_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
425 if (sd_id128_equal(arg_machine_id
, SD_ID128_NULL
))
428 mkdir_parents(etc_machine_id
, 0755);
429 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
), WRITE_STRING_FILE_CREATE
);
431 return log_error_errno(r
, "Failed to write machine id: %m");
433 log_info("%s written.", etc_machine_id
);
437 static int prompt_root_password(void) {
438 const char *msg1
, *msg2
, *etc_shadow
;
441 if (arg_root_password
)
444 if (!arg_prompt_root_password
)
447 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
448 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
454 msg1
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
455 msg2
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter new root password again: ");
458 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
460 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
462 return log_error_errno(r
, "Failed to query root password: %m");
465 log_warning("No password entered, skipping.");
469 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
471 return log_error_errno(r
, "Failed to query root password: %m");
474 log_error("Entered passwords did not match, please try again.");
478 arg_root_password
= a
;
486 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
487 _cleanup_fclose_
FILE *f
= NULL
;
492 f
= fopen(path
, "wex");
497 if (putspent(p
, f
) != 0)
498 return errno
? -errno
: -EIO
;
500 return fflush_and_check(f
);
503 static int process_root_password(void) {
505 static const char table
[] =
506 "abcdefghijklmnopqrstuvwxyz"
507 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
512 .sp_namp
= (char*) "root",
518 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
521 _cleanup_close_
int lock
= -1;
527 const char *etc_shadow
;
530 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
531 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
534 mkdir_parents(etc_shadow
, 0755);
536 lock
= take_password_lock(arg_root
);
540 if (arg_copy_root_password
&& arg_root
) {
544 p
= getspnam("root");
545 if (p
|| errno
!= ENOENT
) {
550 log_error_errno(errno
, "Failed to find shadow entry for root: %m");
554 r
= write_root_shadow(etc_shadow
, p
);
556 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
558 log_info("%s copied.", etc_shadow
);
563 r
= prompt_root_password();
567 if (!arg_root_password
)
570 r
= dev_urandom(raw
, 16);
572 return log_error_errno(r
, "Failed to get salt: %m");
574 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
575 assert_cc(sizeof(table
) == 64 + 1);
576 j
= stpcpy(salt
, "$6$");
577 for (i
= 0; i
< 16; i
++)
578 j
[i
] = table
[raw
[i
] & 63];
583 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
588 log_error_errno(errno
, "Failed to encrypt password: %m");
592 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
594 r
= write_root_shadow(etc_shadow
, &item
);
596 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
598 log_info("%s written.", etc_shadow
);
602 static void help(void) {
603 printf("%s [OPTIONS...]\n\n"
604 "Configures basic settings of the system.\n\n"
605 " -h --help Show this help\n"
606 " --version Show package version\n"
607 " --root=PATH Operate on an alternate filesystem root\n"
608 " --locale=LOCALE Set primary locale (LANG=)\n"
609 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
610 " --timezone=TIMEZONE Set timezone\n"
611 " --hostname=NAME Set host name\n"
612 " --machine-ID=ID Set machine ID\n"
613 " --root-password=PASSWORD Set root password\n"
614 " --root-password-file=FILE Set root password from file\n"
615 " --prompt-locale Prompt the user for locale settings\n"
616 " --prompt-timezone Prompt the user for timezone\n"
617 " --prompt-hostname Prompt the user for hostname\n"
618 " --prompt-root-password Prompt the user for root password\n"
619 " --prompt Prompt for all of the above\n"
620 " --copy-locale Copy locale from host\n"
621 " --copy-timezone Copy timezone from host\n"
622 " --copy-root-password Copy root password from host\n"
623 " --copy Copy locale, timezone, root password\n"
624 " --setup-machine-id Generate a new random machine ID\n"
625 , program_invocation_short_name
);
628 static int parse_argv(int argc
, char *argv
[]) {
639 ARG_ROOT_PASSWORD_FILE
,
644 ARG_PROMPT_ROOT_PASSWORD
,
648 ARG_COPY_ROOT_PASSWORD
,
649 ARG_SETUP_MACHINE_ID
,
652 static const struct option options
[] = {
653 { "help", no_argument
, NULL
, 'h' },
654 { "version", no_argument
, NULL
, ARG_VERSION
},
655 { "root", required_argument
, NULL
, ARG_ROOT
},
656 { "locale", required_argument
, NULL
, ARG_LOCALE
},
657 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
658 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
659 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
660 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
661 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
662 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
663 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
664 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
665 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
666 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
667 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
668 { "copy", no_argument
, NULL
, ARG_COPY
},
669 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
670 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
671 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
672 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
681 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
693 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
699 if (!locale_is_valid(optarg
)) {
700 log_error("Locale %s is not valid.", optarg
);
704 r
= free_and_strdup(&arg_locale
, optarg
);
710 case ARG_LOCALE_MESSAGES
:
711 if (!locale_is_valid(optarg
)) {
712 log_error("Locale %s is not valid.", optarg
);
716 r
= free_and_strdup(&arg_locale_messages
, optarg
);
723 if (!timezone_is_valid(optarg
)) {
724 log_error("Timezone %s is not valid.", optarg
);
728 r
= free_and_strdup(&arg_timezone
, optarg
);
734 case ARG_ROOT_PASSWORD
:
735 r
= free_and_strdup(&arg_root_password
, optarg
);
740 case ARG_ROOT_PASSWORD_FILE
:
741 arg_root_password
= mfree(arg_root_password
);
743 r
= read_one_line_file(optarg
, &arg_root_password
);
745 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
750 if (!hostname_is_valid(optarg
, true)) {
751 log_error("Host name %s is not valid.", optarg
);
755 hostname_cleanup(optarg
);
756 r
= free_and_strdup(&arg_hostname
, optarg
);
763 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
764 log_error("Failed to parse machine id %s.", optarg
);
771 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
774 case ARG_PROMPT_LOCALE
:
775 arg_prompt_locale
= true;
778 case ARG_PROMPT_TIMEZONE
:
779 arg_prompt_timezone
= true;
782 case ARG_PROMPT_HOSTNAME
:
783 arg_prompt_hostname
= true;
786 case ARG_PROMPT_ROOT_PASSWORD
:
787 arg_prompt_root_password
= true;
791 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
794 case ARG_COPY_LOCALE
:
795 arg_copy_locale
= true;
798 case ARG_COPY_TIMEZONE
:
799 arg_copy_timezone
= true;
802 case ARG_COPY_ROOT_PASSWORD
:
803 arg_copy_root_password
= true;
806 case ARG_SETUP_MACHINE_ID
:
808 r
= sd_id128_randomize(&arg_machine_id
);
810 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
818 assert_not_reached("Unhandled option");
824 int main(int argc
, char *argv
[]) {
827 r
= parse_argv(argc
, argv
);
831 log_set_target(LOG_TARGET_AUTO
);
832 log_parse_environment();
837 r
= process_locale();
841 r
= process_timezone();
845 r
= process_hostname();
849 r
= process_machine_id();
853 r
= process_root_password();
860 free(arg_locale_messages
);
863 string_erase(arg_root_password
);
864 free(arg_root_password
);
866 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;