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"
42 static char *arg_root
= NULL
;
43 static char *arg_locale
= NULL
; /* $LANG */
44 static char *arg_locale_messages
= NULL
; /* $LC_MESSAGES */
45 static char *arg_timezone
= NULL
;
46 static char *arg_hostname
= NULL
;
47 static sd_id128_t arg_machine_id
= {};
48 static char *arg_root_password
= NULL
;
49 static bool arg_prompt_locale
= false;
50 static bool arg_prompt_timezone
= false;
51 static bool arg_prompt_hostname
= false;
52 static bool arg_prompt_root_password
= false;
53 static bool arg_copy_locale
= false;
54 static bool arg_copy_timezone
= false;
55 static bool arg_copy_root_password
= false;
57 static bool press_any_key(void) {
61 printf("-- Press any key to proceed --");
64 (void) read_one_char(stdin
, &k
, USEC_INFINITY
, &need_nl
);
72 static void print_welcome(void) {
73 _cleanup_free_
char *pretty_name
= NULL
;
74 const char *os_release
= NULL
;
75 static bool done
= false;
81 os_release
= prefix_roota(arg_root
, "/etc/os-release");
82 r
= parse_env_file(os_release
, NEWLINE
,
83 "PRETTY_NAME", &pretty_name
,
87 os_release
= prefix_roota(arg_root
, "/usr/lib/os-release");
88 r
= parse_env_file(os_release
, NEWLINE
,
89 "PRETTY_NAME", &pretty_name
,
93 if (r
< 0 && r
!= -ENOENT
)
94 log_warning_errno(r
, "Failed to read os-release file: %m");
96 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
97 isempty(pretty_name
) ? "Linux" : pretty_name
);
104 static int show_menu(char **x
, unsigned n_columns
, unsigned width
, unsigned percentage
) {
105 unsigned n
, per_column
, i
, j
;
106 unsigned break_lines
, break_modulo
;
108 assert(n_columns
> 0);
111 per_column
= (n
+ n_columns
- 1) / n_columns
;
113 break_lines
= lines();
117 /* The first page gets two extra lines, since we want to show
119 break_modulo
= break_lines
;
120 if (break_modulo
> 3)
123 for (i
= 0; i
< per_column
; i
++) {
125 for (j
= 0; j
< n_columns
; j
++) {
126 _cleanup_free_
char *e
= NULL
;
128 if (j
* per_column
+ i
>= n
)
131 e
= ellipsize(x
[j
* per_column
+ i
], width
, percentage
);
135 printf("%4u) %-*s", j
* per_column
+ i
+ 1, width
, e
);
140 /* on the first screen we reserve 2 extra lines for the title */
141 if (i
% break_lines
== break_modulo
) {
142 if (!press_any_key())
150 static int prompt_loop(const char *text
, char **l
, bool (*is_valid
)(const char *name
), char **ret
) {
158 _cleanup_free_
char *p
= NULL
;
161 r
= ask_string(&p
, "%s %s (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
), text
);
163 return log_error_errno(r
, "Failed to query user: %m");
166 log_warning("No data entered, skipping.");
170 r
= safe_atou(p
, &u
);
174 if (u
<= 0 || u
> strv_length(l
)) {
175 log_error("Specified entry number out of range.");
179 log_info("Selected '%s'.", l
[u
-1]);
191 log_error("Entered data invalid.");
202 static int prompt_locale(void) {
203 _cleanup_strv_free_
char **locales
= NULL
;
206 if (arg_locale
|| arg_locale_messages
)
209 if (!arg_prompt_locale
)
212 r
= get_locales(&locales
);
214 return log_error_errno(r
, "Cannot query locales list: %m");
218 printf("\nAvailable Locales:\n\n");
219 r
= show_menu(locales
, 3, 22, 60);
225 r
= prompt_loop("Please enter system locale name or number", locales
, locale_is_valid
, &arg_locale
);
229 if (isempty(arg_locale
))
232 r
= prompt_loop("Please enter system message locale name or number", locales
, locale_is_valid
, &arg_locale_messages
);
239 static int process_locale(void) {
240 const char *etc_localeconf
;
245 etc_localeconf
= prefix_roota(arg_root
, "/etc/locale.conf");
246 if (faccessat(AT_FDCWD
, etc_localeconf
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
249 if (arg_copy_locale
&& arg_root
) {
251 mkdir_parents(etc_localeconf
, 0755);
252 r
= copy_file("/etc/locale.conf", etc_localeconf
, 0, 0644, 0);
255 return log_error_errno(r
, "Failed to copy %s: %m", etc_localeconf
);
257 log_info("%s copied.", etc_localeconf
);
266 if (!isempty(arg_locale
))
267 locales
[i
++] = strjoina("LANG=", arg_locale
);
268 if (!isempty(arg_locale_messages
) && !streq(arg_locale_messages
, arg_locale
))
269 locales
[i
++] = strjoina("LC_MESSAGES=", arg_locale_messages
);
276 mkdir_parents(etc_localeconf
, 0755);
277 r
= write_env_file(etc_localeconf
, locales
);
279 return log_error_errno(r
, "Failed to write %s: %m", etc_localeconf
);
281 log_info("%s written.", etc_localeconf
);
285 static int prompt_timezone(void) {
286 _cleanup_strv_free_
char **zones
= NULL
;
292 if (!arg_prompt_timezone
)
295 r
= get_timezones(&zones
);
297 return log_error_errno(r
, "Cannot query timezone list: %m");
301 printf("\nAvailable Time Zones:\n\n");
302 r
= show_menu(zones
, 3, 22, 30);
308 r
= prompt_loop("Please enter timezone name or number", zones
, timezone_is_valid
, &arg_timezone
);
315 static int process_timezone(void) {
316 const char *etc_localtime
, *e
;
319 etc_localtime
= prefix_roota(arg_root
, "/etc/localtime");
320 if (faccessat(AT_FDCWD
, etc_localtime
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
323 if (arg_copy_timezone
&& arg_root
) {
324 _cleanup_free_
char *p
= NULL
;
326 r
= readlink_malloc("/etc/localtime", &p
);
329 return log_error_errno(r
, "Failed to read host timezone: %m");
331 mkdir_parents(etc_localtime
, 0755);
332 if (symlink(p
, etc_localtime
) < 0)
333 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
335 log_info("%s copied.", etc_localtime
);
340 r
= prompt_timezone();
344 if (isempty(arg_timezone
))
347 e
= strjoina("../usr/share/zoneinfo/", arg_timezone
);
349 mkdir_parents(etc_localtime
, 0755);
350 if (symlink(e
, etc_localtime
) < 0)
351 return log_error_errno(errno
, "Failed to create %s symlink: %m", etc_localtime
);
353 log_info("%s written", etc_localtime
);
357 static int prompt_hostname(void) {
363 if (!arg_prompt_hostname
)
370 _cleanup_free_
char *h
= NULL
;
372 r
= ask_string(&h
, "%s Please enter hostname for new system (empty to skip): ", draw_special_char(DRAW_TRIANGULAR_BULLET
));
374 return log_error_errno(r
, "Failed to query hostname: %m");
377 log_warning("No hostname entered, skipping.");
381 if (!hostname_is_valid(h
, true)) {
382 log_error("Specified hostname invalid.");
386 /* Get rid of the trailing dot that we allow, but don't want to see */
387 arg_hostname
= hostname_cleanup(h
);
395 static int process_hostname(void) {
396 const char *etc_hostname
;
399 etc_hostname
= prefix_roota(arg_root
, "/etc/hostname");
400 if (faccessat(AT_FDCWD
, etc_hostname
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
403 r
= prompt_hostname();
407 if (isempty(arg_hostname
))
410 mkdir_parents(etc_hostname
, 0755);
411 r
= write_string_file(etc_hostname
, arg_hostname
, WRITE_STRING_FILE_CREATE
);
413 return log_error_errno(r
, "Failed to write %s: %m", etc_hostname
);
415 log_info("%s written.", etc_hostname
);
419 static int process_machine_id(void) {
420 const char *etc_machine_id
;
421 char id
[SD_ID128_STRING_MAX
];
424 etc_machine_id
= prefix_roota(arg_root
, "/etc/machine-id");
425 if (faccessat(AT_FDCWD
, etc_machine_id
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
428 if (sd_id128_equal(arg_machine_id
, SD_ID128_NULL
))
431 mkdir_parents(etc_machine_id
, 0755);
432 r
= write_string_file(etc_machine_id
, sd_id128_to_string(arg_machine_id
, id
), WRITE_STRING_FILE_CREATE
);
434 return log_error_errno(r
, "Failed to write machine id: %m");
436 log_info("%s written.", etc_machine_id
);
440 static int prompt_root_password(void) {
441 const char *msg1
, *msg2
, *etc_shadow
;
444 if (arg_root_password
)
447 if (!arg_prompt_root_password
)
450 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
451 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
457 msg1
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter a new root password (empty to skip): ");
458 msg2
= strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET
), " Please enter new root password again: ");
461 _cleanup_string_free_erase_
char *a
= NULL
, *b
= NULL
;
463 r
= ask_password_tty(msg1
, NULL
, 0, 0, NULL
, &a
);
465 return log_error_errno(r
, "Failed to query root password: %m");
468 log_warning("No password entered, skipping.");
472 r
= ask_password_tty(msg2
, NULL
, 0, 0, NULL
, &b
);
474 return log_error_errno(r
, "Failed to query root password: %m");
477 log_error("Entered passwords did not match, please try again.");
481 arg_root_password
= a
;
489 static int write_root_shadow(const char *path
, const struct spwd
*p
) {
490 _cleanup_fclose_
FILE *f
= NULL
;
495 f
= fopen(path
, "wex");
500 if (putspent(p
, f
) != 0)
501 return errno
? -errno
: -EIO
;
503 return fflush_and_check(f
);
506 static int process_root_password(void) {
508 static const char table
[] =
509 "abcdefghijklmnopqrstuvwxyz"
510 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
515 .sp_namp
= (char*) "root",
521 .sp_flag
= (unsigned long) -1, /* this appears to be what everybody does ... */
524 _cleanup_close_
int lock
= -1;
530 const char *etc_shadow
;
533 etc_shadow
= prefix_roota(arg_root
, "/etc/shadow");
534 if (faccessat(AT_FDCWD
, etc_shadow
, F_OK
, AT_SYMLINK_NOFOLLOW
) >= 0)
537 mkdir_parents(etc_shadow
, 0755);
539 lock
= take_password_lock(arg_root
);
543 if (arg_copy_root_password
&& arg_root
) {
547 p
= getspnam("root");
548 if (p
|| errno
!= ENOENT
) {
553 log_error_errno(errno
, "Failed to find shadow entry for root: %m");
557 r
= write_root_shadow(etc_shadow
, p
);
559 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
561 log_info("%s copied.", etc_shadow
);
566 r
= prompt_root_password();
570 if (!arg_root_password
)
573 r
= dev_urandom(raw
, 16);
575 return log_error_errno(r
, "Failed to get salt: %m");
577 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
578 assert_cc(sizeof(table
) == 64 + 1);
579 j
= stpcpy(salt
, "$6$");
580 for (i
= 0; i
< 16; i
++)
581 j
[i
] = table
[raw
[i
] & 63];
586 item
.sp_pwdp
= crypt(arg_root_password
, salt
);
591 log_error_errno(errno
, "Failed to encrypt password: %m");
595 item
.sp_lstchg
= (long) (now(CLOCK_REALTIME
) / USEC_PER_DAY
);
597 r
= write_root_shadow(etc_shadow
, &item
);
599 return log_error_errno(r
, "Failed to write %s: %m", etc_shadow
);
601 log_info("%s written.", etc_shadow
);
605 static void help(void) {
606 printf("%s [OPTIONS...]\n\n"
607 "Configures basic settings of the system.\n\n"
608 " -h --help Show this help\n"
609 " --version Show package version\n"
610 " --root=PATH Operate on an alternate filesystem root\n"
611 " --locale=LOCALE Set primary locale (LANG=)\n"
612 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
613 " --timezone=TIMEZONE Set timezone\n"
614 " --hostname=NAME Set host name\n"
615 " --machine-ID=ID Set machine ID\n"
616 " --root-password=PASSWORD Set root password\n"
617 " --root-password-file=FILE Set root password from file\n"
618 " --prompt-locale Prompt the user for locale settings\n"
619 " --prompt-timezone Prompt the user for timezone\n"
620 " --prompt-hostname Prompt the user for hostname\n"
621 " --prompt-root-password Prompt the user for root password\n"
622 " --prompt Prompt for all of the above\n"
623 " --copy-locale Copy locale from host\n"
624 " --copy-timezone Copy timezone from host\n"
625 " --copy-root-password Copy root password from host\n"
626 " --copy Copy locale, timezone, root password\n"
627 " --setup-machine-id Generate a new random machine ID\n"
628 , program_invocation_short_name
);
631 static int parse_argv(int argc
, char *argv
[]) {
642 ARG_ROOT_PASSWORD_FILE
,
647 ARG_PROMPT_ROOT_PASSWORD
,
651 ARG_COPY_ROOT_PASSWORD
,
652 ARG_SETUP_MACHINE_ID
,
655 static const struct option options
[] = {
656 { "help", no_argument
, NULL
, 'h' },
657 { "version", no_argument
, NULL
, ARG_VERSION
},
658 { "root", required_argument
, NULL
, ARG_ROOT
},
659 { "locale", required_argument
, NULL
, ARG_LOCALE
},
660 { "locale-messages", required_argument
, NULL
, ARG_LOCALE_MESSAGES
},
661 { "timezone", required_argument
, NULL
, ARG_TIMEZONE
},
662 { "hostname", required_argument
, NULL
, ARG_HOSTNAME
},
663 { "machine-id", required_argument
, NULL
, ARG_MACHINE_ID
},
664 { "root-password", required_argument
, NULL
, ARG_ROOT_PASSWORD
},
665 { "root-password-file", required_argument
, NULL
, ARG_ROOT_PASSWORD_FILE
},
666 { "prompt", no_argument
, NULL
, ARG_PROMPT
},
667 { "prompt-locale", no_argument
, NULL
, ARG_PROMPT_LOCALE
},
668 { "prompt-timezone", no_argument
, NULL
, ARG_PROMPT_TIMEZONE
},
669 { "prompt-hostname", no_argument
, NULL
, ARG_PROMPT_HOSTNAME
},
670 { "prompt-root-password", no_argument
, NULL
, ARG_PROMPT_ROOT_PASSWORD
},
671 { "copy", no_argument
, NULL
, ARG_COPY
},
672 { "copy-locale", no_argument
, NULL
, ARG_COPY_LOCALE
},
673 { "copy-timezone", no_argument
, NULL
, ARG_COPY_TIMEZONE
},
674 { "copy-root-password", no_argument
, NULL
, ARG_COPY_ROOT_PASSWORD
},
675 { "setup-machine-id", no_argument
, NULL
, ARG_SETUP_MACHINE_ID
},
684 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
696 r
= parse_path_argument_and_warn(optarg
, true, &arg_root
);
702 if (!locale_is_valid(optarg
)) {
703 log_error("Locale %s is not valid.", optarg
);
707 r
= free_and_strdup(&arg_locale
, optarg
);
713 case ARG_LOCALE_MESSAGES
:
714 if (!locale_is_valid(optarg
)) {
715 log_error("Locale %s is not valid.", optarg
);
719 r
= free_and_strdup(&arg_locale_messages
, optarg
);
726 if (!timezone_is_valid(optarg
)) {
727 log_error("Timezone %s is not valid.", optarg
);
731 r
= free_and_strdup(&arg_timezone
, optarg
);
737 case ARG_ROOT_PASSWORD
:
738 r
= free_and_strdup(&arg_root_password
, optarg
);
743 case ARG_ROOT_PASSWORD_FILE
:
744 arg_root_password
= mfree(arg_root_password
);
746 r
= read_one_line_file(optarg
, &arg_root_password
);
748 return log_error_errno(r
, "Failed to read %s: %m", optarg
);
753 if (!hostname_is_valid(optarg
, true)) {
754 log_error("Host name %s is not valid.", optarg
);
758 hostname_cleanup(optarg
);
759 r
= free_and_strdup(&arg_hostname
, optarg
);
766 if (sd_id128_from_string(optarg
, &arg_machine_id
) < 0) {
767 log_error("Failed to parse machine id %s.", optarg
);
774 arg_prompt_locale
= arg_prompt_timezone
= arg_prompt_hostname
= arg_prompt_root_password
= true;
777 case ARG_PROMPT_LOCALE
:
778 arg_prompt_locale
= true;
781 case ARG_PROMPT_TIMEZONE
:
782 arg_prompt_timezone
= true;
785 case ARG_PROMPT_HOSTNAME
:
786 arg_prompt_hostname
= true;
789 case ARG_PROMPT_ROOT_PASSWORD
:
790 arg_prompt_root_password
= true;
794 arg_copy_locale
= arg_copy_timezone
= arg_copy_root_password
= true;
797 case ARG_COPY_LOCALE
:
798 arg_copy_locale
= true;
801 case ARG_COPY_TIMEZONE
:
802 arg_copy_timezone
= true;
805 case ARG_COPY_ROOT_PASSWORD
:
806 arg_copy_root_password
= true;
809 case ARG_SETUP_MACHINE_ID
:
811 r
= sd_id128_randomize(&arg_machine_id
);
813 return log_error_errno(r
, "Failed to generate randomized machine ID: %m");
821 assert_not_reached("Unhandled option");
827 int main(int argc
, char *argv
[]) {
830 r
= parse_argv(argc
, argv
);
834 log_set_target(LOG_TARGET_AUTO
);
835 log_parse_environment();
840 r
= process_locale();
844 r
= process_timezone();
848 r
= process_hostname();
852 r
= process_machine_id();
856 r
= process_root_password();
863 free(arg_locale_messages
);
866 string_erase(arg_root_password
);
867 free(arg_root_password
);
869 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;