]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/firstboot/firstboot.c
firstboot: Include <crypt.h> for declaration of crypt() if needed (#7944)
[thirdparty/systemd.git] / src / firstboot / firstboot.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <fcntl.h>
22 #include <getopt.h>
23 #include <shadow.h>
24 #include <unistd.h>
25
26 #ifdef HAVE_CRYPT_H
27 /* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
28 * removed from glibc at some point. As part of the removal, defines for
29 * crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
30 *
31 * Newer versions of glibc (v2.0+) already ship crypt.h with a definition
32 * of crypt(3) as well, so we simply include it if it is present. MariaDB,
33 * MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
34 * same way since ages without any problems.
35 */
36 # include <crypt.h>
37 #endif
38
39 #include "sd-id128.h"
40
41 #include "alloc-util.h"
42 #include "ask-password-api.h"
43 #include "copy.h"
44 #include "fd-util.h"
45 #include "fileio.h"
46 #include "fs-util.h"
47 #include "hostname-util.h"
48 #include "locale-util.h"
49 #include "mkdir.h"
50 #include "parse-util.h"
51 #include "path-util.h"
52 #include "proc-cmdline.h"
53 #include "random-util.h"
54 #include "string-util.h"
55 #include "strv.h"
56 #include "terminal-util.h"
57 #include "time-util.h"
58 #include "umask-util.h"
59 #include "user-util.h"
60
61 static char *arg_root = NULL;
62 static char *arg_locale = NULL; /* $LANG */
63 static char *arg_keymap = NULL;
64 static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
65 static char *arg_timezone = NULL;
66 static char *arg_hostname = NULL;
67 static sd_id128_t arg_machine_id = {};
68 static char *arg_root_password = NULL;
69 static bool arg_prompt_locale = false;
70 static bool arg_prompt_keymap = false;
71 static bool arg_prompt_timezone = false;
72 static bool arg_prompt_hostname = false;
73 static bool arg_prompt_root_password = false;
74 static bool arg_copy_locale = false;
75 static bool arg_copy_keymap = false;
76 static bool arg_copy_timezone = false;
77 static bool arg_copy_root_password = false;
78
79 static bool press_any_key(void) {
80 char k = 0;
81 bool need_nl = true;
82
83 printf("-- Press any key to proceed --");
84 fflush(stdout);
85
86 (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
87
88 if (need_nl)
89 putchar('\n');
90
91 return k != 'q';
92 }
93
94 static void print_welcome(void) {
95 _cleanup_free_ char *pretty_name = NULL;
96 const char *os_release = NULL;
97 static bool done = false;
98 int r;
99
100 if (done)
101 return;
102
103 os_release = prefix_roota(arg_root, "/etc/os-release");
104 r = parse_env_file(os_release, NEWLINE,
105 "PRETTY_NAME", &pretty_name,
106 NULL);
107 if (r == -ENOENT) {
108
109 os_release = prefix_roota(arg_root, "/usr/lib/os-release");
110 r = parse_env_file(os_release, NEWLINE,
111 "PRETTY_NAME", &pretty_name,
112 NULL);
113 }
114
115 if (r < 0 && r != -ENOENT)
116 log_warning_errno(r, "Failed to read os-release file: %m");
117
118 printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
119 isempty(pretty_name) ? "Linux" : pretty_name);
120
121 press_any_key();
122
123 done = true;
124 }
125
126 static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
127 unsigned n, per_column, i, j;
128 unsigned break_lines, break_modulo;
129
130 assert(n_columns > 0);
131
132 n = strv_length(x);
133 per_column = (n + n_columns - 1) / n_columns;
134
135 break_lines = lines();
136 if (break_lines > 2)
137 break_lines--;
138
139 /* The first page gets two extra lines, since we want to show
140 * a title */
141 break_modulo = break_lines;
142 if (break_modulo > 3)
143 break_modulo -= 3;
144
145 for (i = 0; i < per_column; i++) {
146
147 for (j = 0; j < n_columns; j ++) {
148 _cleanup_free_ char *e = NULL;
149
150 if (j * per_column + i >= n)
151 break;
152
153 e = ellipsize(x[j * per_column + i], width, percentage);
154 if (!e)
155 return log_oom();
156
157 printf("%4u) %-*s", j * per_column + i + 1, width, e);
158 }
159
160 putchar('\n');
161
162 /* on the first screen we reserve 2 extra lines for the title */
163 if (i % break_lines == break_modulo) {
164 if (!press_any_key())
165 return 0;
166 }
167 }
168
169 return 0;
170 }
171
172 static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) {
173 int r;
174
175 assert(text);
176 assert(is_valid);
177 assert(ret);
178
179 for (;;) {
180 _cleanup_free_ char *p = NULL;
181 unsigned u;
182
183 r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
184 if (r < 0)
185 return log_error_errno(r, "Failed to query user: %m");
186
187 if (isempty(p)) {
188 log_warning("No data entered, skipping.");
189 return 0;
190 }
191
192 r = safe_atou(p, &u);
193 if (r >= 0) {
194 char *c;
195
196 if (u <= 0 || u > strv_length(l)) {
197 log_error("Specified entry number out of range.");
198 continue;
199 }
200
201 log_info("Selected '%s'.", l[u-1]);
202
203 c = strdup(l[u-1]);
204 if (!c)
205 return log_oom();
206
207 free(*ret);
208 *ret = c;
209 return 0;
210 }
211
212 if (!is_valid(p)) {
213 log_error("Entered data invalid.");
214 continue;
215 }
216
217 free(*ret);
218 *ret = p;
219 p = 0;
220 return 0;
221 }
222 }
223
224 static int prompt_locale(void) {
225 _cleanup_strv_free_ char **locales = NULL;
226 int r;
227
228 if (arg_locale || arg_locale_messages)
229 return 0;
230
231 if (!arg_prompt_locale)
232 return 0;
233
234 r = get_locales(&locales);
235 if (r < 0)
236 return log_error_errno(r, "Cannot query locales list: %m");
237
238 print_welcome();
239
240 printf("\nAvailable Locales:\n\n");
241 r = show_menu(locales, 3, 22, 60);
242 if (r < 0)
243 return r;
244
245 putchar('\n');
246
247 r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
248 if (r < 0)
249 return r;
250
251 if (isempty(arg_locale))
252 return 0;
253
254 r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
255 if (r < 0)
256 return r;
257
258 return 0;
259 }
260
261 static int process_locale(void) {
262 const char *etc_localeconf;
263 char* locales[3];
264 unsigned i = 0;
265 int r;
266
267 etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
268 if (laccess(etc_localeconf, F_OK) >= 0)
269 return 0;
270
271 if (arg_copy_locale && arg_root) {
272
273 mkdir_parents(etc_localeconf, 0755);
274 r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0, COPY_REFLINK);
275 if (r != -ENOENT) {
276 if (r < 0)
277 return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
278
279 log_info("%s copied.", etc_localeconf);
280 return 0;
281 }
282 }
283
284 r = prompt_locale();
285 if (r < 0)
286 return r;
287
288 if (!isempty(arg_locale))
289 locales[i++] = strjoina("LANG=", arg_locale);
290 if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
291 locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
292
293 if (i == 0)
294 return 0;
295
296 locales[i] = NULL;
297
298 mkdir_parents(etc_localeconf, 0755);
299 r = write_env_file(etc_localeconf, locales);
300 if (r < 0)
301 return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
302
303 log_info("%s written.", etc_localeconf);
304 return 0;
305 }
306
307 static int prompt_keymap(void) {
308 _cleanup_strv_free_ char **kmaps = NULL;
309 int r;
310
311 if (arg_keymap)
312 return 0;
313
314 if (!arg_prompt_keymap)
315 return 0;
316
317 r = get_keymaps(&kmaps);
318 if (r == -ENOENT) /* no keymaps installed */
319 return r;
320 if (r < 0)
321 return log_error_errno(r, "Failed to read keymaps: %m");
322
323 print_welcome();
324
325 printf("\nAvailable keymaps:\n\n");
326 r = show_menu(kmaps, 3, 22, 60);
327 if (r < 0)
328 return r;
329
330 putchar('\n');
331
332 return prompt_loop("Please enter system keymap name or number",
333 kmaps, keymap_is_valid, &arg_keymap);
334 }
335
336 static int process_keymap(void) {
337 const char *etc_vconsoleconf;
338 char **keymap;
339 int r;
340
341 etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
342 if (laccess(etc_vconsoleconf, F_OK) >= 0)
343 return 0;
344
345 if (arg_copy_keymap && arg_root) {
346
347 mkdir_parents(etc_vconsoleconf, 0755);
348 r = copy_file("/etc/vconsole.conf", etc_vconsoleconf, 0, 0644, 0, COPY_REFLINK);
349 if (r != -ENOENT) {
350 if (r < 0)
351 return log_error_errno(r, "Failed to copy %s: %m", etc_vconsoleconf);
352
353 log_info("%s copied.", etc_vconsoleconf);
354 return 0;
355 }
356 }
357
358 r = prompt_keymap();
359 if (r == -ENOENT)
360 return 0; /* don't fail if no keymaps are installed */
361 if (r < 0)
362 return r;
363
364 if (isempty(arg_keymap))
365 return 0;
366
367 keymap = STRV_MAKE(strjoina("KEYMAP=", arg_keymap));
368
369 r = mkdir_parents(etc_vconsoleconf, 0755);
370 if (r < 0)
371 return log_error_errno(r, "Failed to create the parent directory of %s: %m", etc_vconsoleconf);
372
373 r = write_env_file(etc_vconsoleconf, keymap);
374 if (r < 0)
375 return log_error_errno(r, "Failed to write %s: %m", etc_vconsoleconf);
376
377 log_info("%s written.", etc_vconsoleconf);
378 return 0;
379 }
380
381 static int prompt_timezone(void) {
382 _cleanup_strv_free_ char **zones = NULL;
383 int r;
384
385 if (arg_timezone)
386 return 0;
387
388 if (!arg_prompt_timezone)
389 return 0;
390
391 r = get_timezones(&zones);
392 if (r < 0)
393 return log_error_errno(r, "Cannot query timezone list: %m");
394
395 print_welcome();
396
397 printf("\nAvailable Time Zones:\n\n");
398 r = show_menu(zones, 3, 22, 30);
399 if (r < 0)
400 return r;
401
402 putchar('\n');
403
404 r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
405 if (r < 0)
406 return r;
407
408 return 0;
409 }
410
411 static int process_timezone(void) {
412 const char *etc_localtime, *e;
413 int r;
414
415 etc_localtime = prefix_roota(arg_root, "/etc/localtime");
416 if (laccess(etc_localtime, F_OK) >= 0)
417 return 0;
418
419 if (arg_copy_timezone && arg_root) {
420 _cleanup_free_ char *p = NULL;
421
422 r = readlink_malloc("/etc/localtime", &p);
423 if (r != -ENOENT) {
424 if (r < 0)
425 return log_error_errno(r, "Failed to read host timezone: %m");
426
427 mkdir_parents(etc_localtime, 0755);
428 if (symlink(p, etc_localtime) < 0)
429 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
430
431 log_info("%s copied.", etc_localtime);
432 return 0;
433 }
434 }
435
436 r = prompt_timezone();
437 if (r < 0)
438 return r;
439
440 if (isempty(arg_timezone))
441 return 0;
442
443 e = strjoina("../usr/share/zoneinfo/", arg_timezone);
444
445 mkdir_parents(etc_localtime, 0755);
446 if (symlink(e, etc_localtime) < 0)
447 return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
448
449 log_info("%s written", etc_localtime);
450 return 0;
451 }
452
453 static int prompt_hostname(void) {
454 int r;
455
456 if (arg_hostname)
457 return 0;
458
459 if (!arg_prompt_hostname)
460 return 0;
461
462 print_welcome();
463 putchar('\n');
464
465 for (;;) {
466 _cleanup_free_ char *h = NULL;
467
468 r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
469 if (r < 0)
470 return log_error_errno(r, "Failed to query hostname: %m");
471
472 if (isempty(h)) {
473 log_warning("No hostname entered, skipping.");
474 break;
475 }
476
477 if (!hostname_is_valid(h, true)) {
478 log_error("Specified hostname invalid.");
479 continue;
480 }
481
482 /* Get rid of the trailing dot that we allow, but don't want to see */
483 arg_hostname = hostname_cleanup(h);
484 h = NULL;
485 break;
486 }
487
488 return 0;
489 }
490
491 static int process_hostname(void) {
492 const char *etc_hostname;
493 int r;
494
495 etc_hostname = prefix_roota(arg_root, "/etc/hostname");
496 if (laccess(etc_hostname, F_OK) >= 0)
497 return 0;
498
499 r = prompt_hostname();
500 if (r < 0)
501 return r;
502
503 if (isempty(arg_hostname))
504 return 0;
505
506 mkdir_parents(etc_hostname, 0755);
507 r = write_string_file(etc_hostname, arg_hostname,
508 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
509 if (r < 0)
510 return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
511
512 log_info("%s written.", etc_hostname);
513 return 0;
514 }
515
516 static int process_machine_id(void) {
517 const char *etc_machine_id;
518 char id[SD_ID128_STRING_MAX];
519 int r;
520
521 etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
522 if (laccess(etc_machine_id, F_OK) >= 0)
523 return 0;
524
525 if (sd_id128_is_null(arg_machine_id))
526 return 0;
527
528 mkdir_parents(etc_machine_id, 0755);
529 r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
530 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC);
531 if (r < 0)
532 return log_error_errno(r, "Failed to write machine id: %m");
533
534 log_info("%s written.", etc_machine_id);
535 return 0;
536 }
537
538 static int prompt_root_password(void) {
539 const char *msg1, *msg2, *etc_shadow;
540 int r;
541
542 if (arg_root_password)
543 return 0;
544
545 if (!arg_prompt_root_password)
546 return 0;
547
548 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
549 if (laccess(etc_shadow, F_OK) >= 0)
550 return 0;
551
552 print_welcome();
553 putchar('\n');
554
555 msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
556 msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: ");
557
558 for (;;) {
559 _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
560
561 r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
562 if (r < 0)
563 return log_error_errno(r, "Failed to query root password: %m");
564
565 if (isempty(a)) {
566 log_warning("No password entered, skipping.");
567 break;
568 }
569
570 r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
571 if (r < 0)
572 return log_error_errno(r, "Failed to query root password: %m");
573
574 if (!streq(a, b)) {
575 log_error("Entered passwords did not match, please try again.");
576 continue;
577 }
578
579 arg_root_password = a;
580 a = NULL;
581 break;
582 }
583
584 return 0;
585 }
586
587 static int write_root_shadow(const char *path, const struct spwd *p) {
588 _cleanup_fclose_ FILE *f = NULL;
589 assert(path);
590 assert(p);
591
592 RUN_WITH_UMASK(0777)
593 f = fopen(path, "wex");
594 if (!f)
595 return -errno;
596
597 errno = 0;
598 if (putspent(p, f) != 0)
599 return errno > 0 ? -errno : -EIO;
600
601 return fflush_sync_and_check(f);
602 }
603
604 static int process_root_password(void) {
605
606 static const char table[] =
607 "abcdefghijklmnopqrstuvwxyz"
608 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
609 "0123456789"
610 "./";
611
612 struct spwd item = {
613 .sp_namp = (char*) "root",
614 .sp_min = -1,
615 .sp_max = -1,
616 .sp_warn = -1,
617 .sp_inact = -1,
618 .sp_expire = -1,
619 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
620 };
621
622 _cleanup_close_ int lock = -1;
623 char salt[3+16+1+1];
624 uint8_t raw[16];
625 unsigned i;
626 char *j;
627
628 const char *etc_shadow;
629 int r;
630
631 etc_shadow = prefix_roota(arg_root, "/etc/shadow");
632 if (laccess(etc_shadow, F_OK) >= 0)
633 return 0;
634
635 mkdir_parents(etc_shadow, 0755);
636
637 lock = take_etc_passwd_lock(arg_root);
638 if (lock < 0)
639 return log_error_errno(lock, "Failed to take a lock: %m");
640
641 if (arg_copy_root_password && arg_root) {
642 struct spwd *p;
643
644 errno = 0;
645 p = getspnam("root");
646 if (p || errno != ENOENT) {
647 if (!p) {
648 if (!errno)
649 errno = EIO;
650
651 return log_error_errno(errno, "Failed to find shadow entry for root: %m");
652 }
653
654 r = write_root_shadow(etc_shadow, p);
655 if (r < 0)
656 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
657
658 log_info("%s copied.", etc_shadow);
659 return 0;
660 }
661 }
662
663 r = prompt_root_password();
664 if (r < 0)
665 return r;
666
667 if (!arg_root_password)
668 return 0;
669
670 r = acquire_random_bytes(raw, 16, true);
671 if (r < 0)
672 return log_error_errno(r, "Failed to get salt: %m");
673
674 /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
675 assert_cc(sizeof(table) == 64 + 1);
676 j = stpcpy(salt, "$6$");
677 for (i = 0; i < 16; i++)
678 j[i] = table[raw[i] & 63];
679 j[i++] = '$';
680 j[i] = 0;
681
682 errno = 0;
683 item.sp_pwdp = crypt(arg_root_password, salt);
684 if (!item.sp_pwdp) {
685 if (!errno)
686 errno = EINVAL;
687
688 return log_error_errno(errno, "Failed to encrypt password: %m");
689 }
690
691 item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
692
693 r = write_root_shadow(etc_shadow, &item);
694 if (r < 0)
695 return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
696
697 log_info("%s written.", etc_shadow);
698 return 0;
699 }
700
701 static void help(void) {
702 printf("%s [OPTIONS...]\n\n"
703 "Configures basic settings of the system.\n\n"
704 " -h --help Show this help\n"
705 " --version Show package version\n"
706 " --root=PATH Operate on an alternate filesystem root\n"
707 " --locale=LOCALE Set primary locale (LANG=)\n"
708 " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
709 " --keymap=KEYMAP Set keymap\n"
710 " --timezone=TIMEZONE Set timezone\n"
711 " --hostname=NAME Set host name\n"
712 " --machine-ID=ID Set machine ID\n"
713 " --root-password=PASSWORD Set root password\n"
714 " --root-password-file=FILE Set root password from file\n"
715 " --prompt-locale Prompt the user for locale settings\n"
716 " --prompt-keymap Prompt the user for keymap settings\n"
717 " --prompt-timezone Prompt the user for timezone\n"
718 " --prompt-hostname Prompt the user for hostname\n"
719 " --prompt-root-password Prompt the user for root password\n"
720 " --prompt Prompt for all of the above\n"
721 " --copy-locale Copy locale from host\n"
722 " --copy-keymap Copy keymap from host\n"
723 " --copy-timezone Copy timezone from host\n"
724 " --copy-root-password Copy root password from host\n"
725 " --copy Copy locale, keymap, timezone, root password\n"
726 " --setup-machine-id Generate a new random machine ID\n"
727 , program_invocation_short_name);
728 }
729
730 static int parse_argv(int argc, char *argv[]) {
731
732 enum {
733 ARG_VERSION = 0x100,
734 ARG_ROOT,
735 ARG_LOCALE,
736 ARG_LOCALE_MESSAGES,
737 ARG_KEYMAP,
738 ARG_TIMEZONE,
739 ARG_HOSTNAME,
740 ARG_MACHINE_ID,
741 ARG_ROOT_PASSWORD,
742 ARG_ROOT_PASSWORD_FILE,
743 ARG_PROMPT,
744 ARG_PROMPT_LOCALE,
745 ARG_PROMPT_KEYMAP,
746 ARG_PROMPT_TIMEZONE,
747 ARG_PROMPT_HOSTNAME,
748 ARG_PROMPT_ROOT_PASSWORD,
749 ARG_COPY,
750 ARG_COPY_LOCALE,
751 ARG_COPY_KEYMAP,
752 ARG_COPY_TIMEZONE,
753 ARG_COPY_ROOT_PASSWORD,
754 ARG_SETUP_MACHINE_ID,
755 };
756
757 static const struct option options[] = {
758 { "help", no_argument, NULL, 'h' },
759 { "version", no_argument, NULL, ARG_VERSION },
760 { "root", required_argument, NULL, ARG_ROOT },
761 { "locale", required_argument, NULL, ARG_LOCALE },
762 { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
763 { "keymap", required_argument, NULL, ARG_KEYMAP },
764 { "timezone", required_argument, NULL, ARG_TIMEZONE },
765 { "hostname", required_argument, NULL, ARG_HOSTNAME },
766 { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
767 { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
768 { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
769 { "prompt", no_argument, NULL, ARG_PROMPT },
770 { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
771 { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
772 { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
773 { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
774 { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
775 { "copy", no_argument, NULL, ARG_COPY },
776 { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
777 { "copy-keymap", no_argument, NULL, ARG_COPY_KEYMAP },
778 { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
779 { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
780 { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
781 {}
782 };
783
784 int r, c;
785
786 assert(argc >= 0);
787 assert(argv);
788
789 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
790
791 switch (c) {
792
793 case 'h':
794 help();
795 return 0;
796
797 case ARG_VERSION:
798 return version();
799
800 case ARG_ROOT:
801 r = parse_path_argument_and_warn(optarg, true, &arg_root);
802 if (r < 0)
803 return r;
804 break;
805
806 case ARG_LOCALE:
807 if (!locale_is_valid(optarg)) {
808 log_error("Locale %s is not valid.", optarg);
809 return -EINVAL;
810 }
811
812 r = free_and_strdup(&arg_locale, optarg);
813 if (r < 0)
814 return log_oom();
815
816 break;
817
818 case ARG_LOCALE_MESSAGES:
819 if (!locale_is_valid(optarg)) {
820 log_error("Locale %s is not valid.", optarg);
821 return -EINVAL;
822 }
823
824 r = free_and_strdup(&arg_locale_messages, optarg);
825 if (r < 0)
826 return log_oom();
827
828 break;
829
830 case ARG_KEYMAP:
831 if (!keymap_is_valid(optarg)) {
832 log_error("Keymap %s is not valid.", optarg);
833 return -EINVAL;
834 }
835
836 r = free_and_strdup(&arg_keymap, optarg);
837 if (r < 0)
838 return log_oom();
839
840 break;
841
842 case ARG_TIMEZONE:
843 if (!timezone_is_valid(optarg)) {
844 log_error("Timezone %s is not valid.", optarg);
845 return -EINVAL;
846 }
847
848 r = free_and_strdup(&arg_timezone, optarg);
849 if (r < 0)
850 return log_oom();
851
852 break;
853
854 case ARG_ROOT_PASSWORD:
855 r = free_and_strdup(&arg_root_password, optarg);
856 if (r < 0)
857 return log_oom();
858 break;
859
860 case ARG_ROOT_PASSWORD_FILE:
861 arg_root_password = mfree(arg_root_password);
862
863 r = read_one_line_file(optarg, &arg_root_password);
864 if (r < 0)
865 return log_error_errno(r, "Failed to read %s: %m", optarg);
866
867 break;
868
869 case ARG_HOSTNAME:
870 if (!hostname_is_valid(optarg, true)) {
871 log_error("Host name %s is not valid.", optarg);
872 return -EINVAL;
873 }
874
875 hostname_cleanup(optarg);
876 r = free_and_strdup(&arg_hostname, optarg);
877 if (r < 0)
878 return log_oom();
879
880 break;
881
882 case ARG_MACHINE_ID:
883 if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
884 log_error("Failed to parse machine id %s.", optarg);
885 return -EINVAL;
886 }
887
888 break;
889
890 case ARG_PROMPT:
891 arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
892 break;
893
894 case ARG_PROMPT_LOCALE:
895 arg_prompt_locale = true;
896 break;
897
898 case ARG_PROMPT_KEYMAP:
899 arg_prompt_keymap = true;
900 break;
901
902 case ARG_PROMPT_TIMEZONE:
903 arg_prompt_timezone = true;
904 break;
905
906 case ARG_PROMPT_HOSTNAME:
907 arg_prompt_hostname = true;
908 break;
909
910 case ARG_PROMPT_ROOT_PASSWORD:
911 arg_prompt_root_password = true;
912 break;
913
914 case ARG_COPY:
915 arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password = true;
916 break;
917
918 case ARG_COPY_LOCALE:
919 arg_copy_locale = true;
920 break;
921
922 case ARG_COPY_KEYMAP:
923 arg_copy_keymap = true;
924 break;
925
926 case ARG_COPY_TIMEZONE:
927 arg_copy_timezone = true;
928 break;
929
930 case ARG_COPY_ROOT_PASSWORD:
931 arg_copy_root_password = true;
932 break;
933
934 case ARG_SETUP_MACHINE_ID:
935
936 r = sd_id128_randomize(&arg_machine_id);
937 if (r < 0)
938 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
939
940 break;
941
942 case '?':
943 return -EINVAL;
944
945 default:
946 assert_not_reached("Unhandled option");
947 }
948
949 return 1;
950 }
951
952 int main(int argc, char *argv[]) {
953 bool enabled;
954 int r;
955
956 r = parse_argv(argc, argv);
957 if (r <= 0)
958 goto finish;
959
960 log_set_target(LOG_TARGET_AUTO);
961 log_parse_environment();
962 log_open();
963
964 umask(0022);
965
966 r = proc_cmdline_get_bool("systemd.firstboot", &enabled);
967 if (r < 0) {
968 log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring.");
969 goto finish;
970 }
971 if (r > 0 && !enabled) {
972 r = 0; /* disabled */
973 goto finish;
974 }
975
976 r = process_locale();
977 if (r < 0)
978 goto finish;
979
980 r = process_keymap();
981 if (r < 0)
982 goto finish;
983
984 r = process_timezone();
985 if (r < 0)
986 goto finish;
987
988 r = process_hostname();
989 if (r < 0)
990 goto finish;
991
992 r = process_machine_id();
993 if (r < 0)
994 goto finish;
995
996 r = process_root_password();
997 if (r < 0)
998 goto finish;
999
1000 finish:
1001 free(arg_root);
1002 free(arg_locale);
1003 free(arg_locale_messages);
1004 free(arg_keymap);
1005 free(arg_timezone);
1006 free(arg_hostname);
1007 string_erase(arg_root_password);
1008 free(arg_root_password);
1009
1010 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1011 }