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