diff --git a/board/common/users b/board/common/users new file mode 100644 index 00000000..a4f05e6e --- /dev/null +++ b/board/common/users @@ -0,0 +1 @@ +factory -1 wheel -1 =reset - /sbin/factory - Reset to factory defaults diff --git a/board/common/xattrs b/board/common/xattrs new file mode 100644 index 00000000..b38f8f61 --- /dev/null +++ b/board/common/xattrs @@ -0,0 +1,3 @@ +# Allow users in wheel group to reboot and perform a factory reset +/sbin/initctl f 4750 root wheel - - - - - +/sbin/factory f 4750 root wheel - - - - - diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index 7f4b089c..00bc42d7 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -11,10 +11,12 @@ BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_INFIX_PATH)/patches" BR2_TARGET_GENERIC_HOSTNAME="infix" BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit" BR2_INIT_FINIT=y +BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt $(BR2_EXTERNAL_INFIX_PATH)/board/common/xattrs" BR2_SYSTEM_BIN_SH_BASH=y BR2_ENABLE_LOCALE_WHITELIST="C en_US en_CA" BR2_GENERATE_LOCALE="en_US en_CA" BR2_TARGET_TZ_INFO=y +BR2_ROOTFS_USERS_TABLES="$(BR2_EXTERNAL_INFIX_PATH)/board/common/users" BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_INFIX_PATH)/board/common/rootfs" BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_INFIX_PATH)/board/common/post-build.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="$(TOPDIR)/support/scripts/genimage.sh $(BR2_EXTERNAL_INFIX_PATH)/board/common/post-image.sh" @@ -63,6 +65,7 @@ BR2_TARGET_ROOTFS_SQUASHFS=y BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y BR2_PACKAGE_HOST_GENEXT2FS=y BR2_PACKAGE_HOST_GENIMAGE=y +BR2_PACKAGE_FACTORY=y BR2_PACKAGE_FINIT_SULOGIN=y BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y diff --git a/configs/amd64_defconfig b/configs/amd64_defconfig index 07ef3e42..568ab7bf 100644 --- a/configs/amd64_defconfig +++ b/configs/amd64_defconfig @@ -10,11 +10,13 @@ BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_INFIX_PATH)/patches" BR2_TARGET_GENERIC_HOSTNAME="infix" BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit" BR2_INIT_FINIT=y +BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt $(BR2_EXTERNAL_INFIX_PATH)/board/common/xattrs" BR2_SYSTEM_BIN_SH_BASH=y BR2_SYSTEM_DHCP="eth0" BR2_ENABLE_LOCALE_WHITELIST="C en_US en_CA" BR2_GENERATE_LOCALE="en_US en_CA" BR2_TARGET_TZ_INFO=y +BR2_ROOTFS_USERS_TABLES="$(BR2_EXTERNAL_INFIX_PATH)/board/common/users" BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_INFIX_PATH)/board/common/rootfs" BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh $(BR2_EXTERNAL_INFIX_PATH)/board/common/post-build.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="board/qemu/post-image.sh $(BR2_EXTERNAL_INFIX_PATH)/board/common/post-image.sh" @@ -56,6 +58,7 @@ BR2_TARGET_ROOTFS_EXT2=y BR2_TARGET_ROOTFS_EXT2_SIZE="128M" # BR2_TARGET_ROOTFS_TAR is not set BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y +BR2_PACKAGE_FACTORY=y BR2_PACKAGE_FINIT_SULOGIN=y BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y diff --git a/package/Config.in b/package/Config.in index 889af08e..723ca11a 100644 --- a/package/Config.in +++ b/package/Config.in @@ -1,3 +1,4 @@ +source "$BR2_EXTERNAL_INFIX_PATH/package/factory/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/finit/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/ifupdown2/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/mdnsd/Config.in" diff --git a/package/factory/Config.in b/package/factory/Config.in new file mode 100644 index 00000000..1361fa15 --- /dev/null +++ b/package/factory/Config.in @@ -0,0 +1,10 @@ +config BR2_PACKAGE_FACTORY + bool "factory-reset" + help + Simple factory reset program/login-shell for instructing the + system to factory reset itself, and reboot. + + The program is made to run as SUID root to be able to tell the + system to reboot. Sufficient care has been taken to make the + factory program as secure as possible. With the worst possible + eftect to only be abused to reboot the system. diff --git a/package/factory/factory.mk b/package/factory/factory.mk new file mode 100644 index 00000000..7c0928b8 --- /dev/null +++ b/package/factory/factory.mk @@ -0,0 +1,21 @@ +################################################################################ +# +# factory +# +################################################################################ +FACTORY_VERSION = 1.0 +FACTORY_LICENSE = MIT +FACTORY_SITE_METHOD = local +FACTORY_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/factory + +define FACTORY_BUILD_CMDS + $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \ + LDLIBS="$(TARGET_LDFLAGS)" +endef + +define FACTORY_INSTALL_TARGET_CMDS + $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \ + DESTDIR="$(TARGET_DIR)" install +endef + +$(eval $(generic-package)) diff --git a/src/factory/LICENSE b/src/factory/LICENSE new file mode 100644 index 00000000..06121c99 --- /dev/null +++ b/src/factory/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Westermo Network Technologies +Copyright (c) 2023 Joachim Wiberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/factory/Makefile b/src/factory/Makefile new file mode 100644 index 00000000..a144debb --- /dev/null +++ b/src/factory/Makefile @@ -0,0 +1,12 @@ +CFLAGS += -Wall -Wextra -Werror + +all: factory + +clean: + -rm factory + +distclean: clean + -rm *~ + +install: + install -D factory $(DESTDIR)/sbin/ diff --git a/src/factory/factory.c b/src/factory/factory.c new file mode 100644 index 00000000..c70ec12b --- /dev/null +++ b/src/factory/factory.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RESETME "/rw/infix/.reset" +#define touch(f) mknod((f), S_IFREG|0644, 0) + +char rawgetch(void) +{ + struct termios saved, c; + char key; + + if (tcgetattr(fileno(stdin), &saved) < 0) + return -1; + + c = saved; + c.c_lflag &= ~ICANON; + c.c_lflag &= ~ECHO; + c.c_cc[VMIN] = 1; + c.c_cc[VTIME] = 0; + + if (tcsetattr(fileno(stdin), TCSANOW, &c) < 0) { + tcsetattr(fileno(stdin), TCSANOW, &saved); + return -1; + } + + key = getchar(); + tcsetattr(fileno(stdin), TCSANOW, &saved); + + return key; +} + +int yorn(const char *prompt) +{ + char yorn; + + fputs(prompt, stderr); + + yorn = rawgetch(); + fprintf(stderr, "%c\n", yorn); + if (yorn != 'y' && yorn != 'Y') + return 0; + + return 1; +} + +static int usage(int rc) +{ + printf("usage: factory [opts]\n" + "\n" + "Options:\n" + " -h, --help This help text\n" + " -r, --no-reboot Don't reboot. Reboot manually to activate.\n" + " -y, --assume-yes Automatic yes to prompts; assume \"yes\" as answer to all\n" + " prompts and run non-interactively\n" + "\n" + "Note: this program initiates a factory reset by raising a flag and rebooting\n" + " the system. When it comes back up it safely removes all the OverlayFS\n" + " worker/upper directories for /etc, /home, and /var\n"); + + return rc; +} + +static int run(const char *cmd) +{ + int status = system(cmd); + int rc; + + rc = WEXITSTATUS(status); + + if (WIFEXITED(status)) + return rc; + + if (WIFSIGNALED(status)) { + if (rc == 0) + rc = 1; /* adjust, we were aborted */ + } + + return rc; +} + +int main(int argc, char *argv[]) +{ + struct option long_opts[] = { + { "help", 0, NULL, 'h' }, + { "no-reboot", 0, NULL, 'r' }, + { "assume-yes", 0, NULL, 'y' }, + { NULL, 0, NULL, 0 } + }; + int reboot = 1; + int yes = 0; + char *tty; + int c; + + while ((c = getopt_long(argc, argv, "h?ry", long_opts, NULL)) != EOF) { + switch (c) { + case 'h': + case '?': + return usage(0); + + case 'r': + reboot = 0; + break; + + case 'y': + yes = 1; + break; + + default: + return usage(1); + } + } + + tty = ttyname(STDIN_FILENO); + if (!tty && errno == ENOTTY) + yes = 1; + + if (argv[0][0] == '-' && tty && strcmp(tty, "/dev/console")) + errx(1, "factory reset only allowed from console login!"); + + if (yes || yorn("Factory reset device (y/N)? ")) { + if (touch(RESETME) && errno != EEXIST) + err(1, "failed"); + + warnx("scheduled factory reset on next boot."); + if (reboot && (yes || yorn("Reboot now to perform reset, (y/N)? "))) + return run("/sbin/reboot"); + + warnx("remember to reboot the system to perform the factory reset."); + } + + return 0; +} + +/** + * Local Variables: + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */