diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml new file mode 100644 index 0000000..3a29607 --- /dev/null +++ b/.forgejo/workflows/build.yaml @@ -0,0 +1,40 @@ +name: build liminix +on: + pull_request: + types: [opened, synchronize, edited, reopened] + branches: + - main + push: + branches: + - main + +jobs: + build_vm_qemu_mips: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A qemu + + build_zyxel-nwa50ax_mips: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A qemu + + test_hostapd: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A wlan diff --git a/ci.nix b/ci.nix index ec345dd..73d8c97 100644 --- a/ci.nix +++ b/ci.nix @@ -1,21 +1,15 @@ { - nixpkgs -, unstable -, liminix + sources ? import ./lon.nix +, nixpkgs ? sources.nixpkgs +, unstable ? nixpkgs +, liminix ? ./. , ... }: let - inherit (builtins) map; - pkgs = (import nixpkgs {}); + pkgs = (import nixpkgs { }); borderVmConf = ./bordervm.conf-example.nix; - inherit (pkgs.lib.attrsets) genAttrs; + inherit (pkgs.lib.attrsets) genAttrs mapAttrs; devices = [ - "gl-ar750" - "gl-mt300a" - "gl-mt300n-v2" "qemu" - "qemu-aarch64" - "qemu-armv7l" - "tp-archer-ax23" "zyxel-nwa50ax" ]; vanilla = ./vanilla-configuration.nix; @@ -25,7 +19,7 @@ let device = import (liminix + "/devices/${name}"); liminix-config = vanilla; }).outputs.default; - tests = import ./tests/ci.nix; + tests = mapAttrs (_: v: v { inherit liminix nixpkgs; }) (import ./tests/ci.nix); jobs = (genAttrs devices for-device) // tests // @@ -44,12 +38,6 @@ let imports = [ ./modules/all-modules.nix ]; }; }).outputs.optionsJson; - installers = map (f: "system.outputs.${f}") [ - "vmroot" - "mtdimage" - "ubimage" - ]; - inherit (pkgs.lib) concatStringsSep; in pkgs.stdenv.mkDerivation { name = "liminix-doc"; nativeBuildInputs = with pkgs; [ diff --git a/default.nix b/default.nix index e372095..fd1c357 100644 --- a/default.nix +++ b/default.nix @@ -26,9 +26,13 @@ let eval = evalModules { modules = [ { - nixpkgs.overlays = [ - overlay - ]; + nixpkgs = { + source = nixpkgs; + overlays = [ overlay ]; + config.permittedInsecurePackages = [ + "python-2.7.18.8" + ]; + }; } device.module liminix-config diff --git a/devices/qemu/default.nix b/devices/qemu/default.nix index b6e860d..fe98952 100644 --- a/devices/qemu/default.nix +++ b/devices/qemu/default.nix @@ -1,7 +1,7 @@ # This "device" generates images that can be used with the QEMU # emulator. The default output is a directory containing separate # kernel (uncompressed vmlinux) and initrd (squashfs) images -{ +rec { system = { crossSystem = { config = "mips-unknown-linux-musl"; @@ -41,6 +41,9 @@ ../../modules/arch/mipseb.nix ../families/qemu.nix ]; + + nixpkgs.hostPlatform = system.crossSystem; + kernel = { config = { MIPS_MALTA= "y"; diff --git a/devices/zyxel-nwa50ax/default.nix b/devices/zyxel-nwa50ax/default.nix index e58430f..2b4813d 100644 --- a/devices/zyxel-nwa50ax/default.nix +++ b/devices/zyxel-nwa50ax/default.nix @@ -1,4 +1,4 @@ -{ +rec { system = { crossSystem = { config = "mipsel-unknown-linux-musl"; @@ -135,6 +135,8 @@ ../../modules/zyxel-dual-image ]; + nixpkgs.hostPlatform = system.crossSystem; + filesystem = dir { lib = dir { firmware = dir { @@ -170,7 +172,7 @@ maxLEBcount = "256"; }; - flash.eraseBlockSize = 65536; + flash.eraseBlockSize = 64 * 1024; # This is a FIT containing a kernel padded and # a UBI volume rootfs. @@ -181,8 +183,8 @@ # Aligned on 2kb. alignment = 2048; - rootDevice = "ubi:rootfs"; - alternativeRootDevice = "ubi:rootfs"; + rootDevice = "ubi0:rootfs"; + alternativeRootDevice = "ubi1:rootfs"; # Auto-attach MTD devices: ubi_a then ubi_b. ubi.mtds = [ "ubi_a" "ubi_b" ]; @@ -222,8 +224,9 @@ imageFormat = "fit"; tftp = { - # 5MB is nice. - freeSpaceBytes = 5 * 1024 * 1024; + # 20MB is pretty good on this device as we have plenty of RAM. + freeSpaceBytes = 20 * 1024 * 1024; + appendDTB = true; loadAddress = lim.parseInt "0x2000000"; }; }; diff --git a/lib/eval-config.nix b/lib/eval-config.nix index 7befab1..4c3c009 100644 --- a/lib/eval-config.nix +++ b/lib/eval-config.nix @@ -1,4 +1,4 @@ -{ nixpkgs ? , pkgs ? (import {}), lib ? pkgs.lib }: +{ nixpkgs ? , pkgs ? (import nixpkgs {}), lib ? pkgs.lib }: args: let modulesPath = builtins.toString ../modules; diff --git a/lon.lock b/lon.lock new file mode 100644 index 0000000..50209e4 --- /dev/null +++ b/lon.lock @@ -0,0 +1,15 @@ +{ + "version": "1", + "sources": { + "nixpkgs": { + "type": "GitHub", + "fetchType": "tarball", + "owner": "nixos", + "repo": "nixpkgs", + "branch": "nixos-unstable-small", + "revision": "b6227cadb5123c7e4cb159bf6f9f5705ae081a47", + "url": "https://github.com/nixos/nixpkgs/archive/b6227cadb5123c7e4cb159bf6f9f5705ae081a47.tar.gz", + "hash": "sha256-KFR30GNFhjzXl0kVEn+KK4xrFr0gggb1NBroP8ukbxY=" + } + } +} diff --git a/lon.nix b/lon.nix new file mode 100644 index 0000000..5f320ea --- /dev/null +++ b/lon.nix @@ -0,0 +1,41 @@ +# Generated by lon. Do not modify! +let + + lock = builtins.fromJSON (builtins.readFile ./lon.lock); + + # Override with a path defined in an environment variable. If no variable is + # set, the original path is used. + overrideFromEnv = + name: path: + let + replacement = builtins.getEnv "LON_OVERRIDE_${name}"; + in + if replacement == "" then + path + else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 replacement == "/" then + /. + replacement + else + /. + builtins.getEnv "PWD" + "/${replacement}"; + + fetchSource = + args@{ fetchType, ... }: + if fetchType == "git" then + builtins.fetchGit { + url = args.url; + ref = args.branch; + rev = args.revision; + narHash = args.hash; + } + else if fetchType == "tarball" then + builtins.fetchTarball { + url = args.url; + sha256 = args.hash; + } + else + builtins.throw "Unsupported source type ${fetchType}"; + +in +builtins.mapAttrs (name: args: overrideFromEnv name (fetchSource args)) lock.sources diff --git a/modules/all-modules.nix b/modules/all-modules.nix index abce5db..98352c1 100644 --- a/modules/all-modules.nix +++ b/modules/all-modules.nix @@ -8,6 +8,7 @@ ./bridge ./busybox.nix ./dhcp6c + ./jitter-rng ./dnsmasq ./firewall ./hardware.nix diff --git a/modules/arch/mips.nix b/modules/arch/mips.nix index 155c147..359bb71 100644 --- a/modules/arch/mips.nix +++ b/modules/arch/mips.nix @@ -14,5 +14,8 @@ boot.commandLine = [ "console=ttyS0,115200" # true of all mips we've yet encountered ]; + boot.tftp.commandLine = [ + "console=ttyS0,115200" # true of all mips we've yet encountered + ]; }; } diff --git a/modules/base.nix b/modules/base.nix index 1c8b67c..41550e6 100644 --- a/modules/base.nix +++ b/modules/base.nix @@ -69,6 +69,14 @@ in { default = "uimage"; }; tftp = { + commandLine = mkOption { + type = types.listOf types.str; + default = config.boot.commandLine; + description = '' + TFTP-specific command line. + Defaults to the classical one if unset. + ''; + }; loadAddress = mkOption { type = types.ints.unsigned; description = '' @@ -98,6 +106,9 @@ in { }; }; config = { + # By default, we enable cross-compilation support. + nixpkgs.buildPlatform = lib.mkDefault builtins.currentSystem; + defaultProfile.packages = with pkgs; [ s6 s6-init-bin execline s6-linux-init s6-rc ]; @@ -109,7 +120,13 @@ in { ] ++ (map (mtd: "ubi.mtd=${mtd}") config.hardware.ubi.mtds) ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}" - ++ lib.optional (config.hardware.alternativeRootDevice != null) "altroot=${config.hardware.alternativeRootDevice}"; + ++ lib.optional (config.hardware.alternativeRootDevice != null) "rootalt=${config.hardware.alternativeRootDevice}"; + + boot.tftp.commandLine = [ + "panic=10 oops=panic init=/bin/init loglevel=8" + "fw_devlink=off" + "rootfstype=${config.rootfsType}" + ]; system.callService = path : parameters : let diff --git a/modules/bridge/default.nix b/modules/bridge/default.nix index 32df4a5..3a24d7c 100644 --- a/modules/bridge/default.nix +++ b/modules/bridge/default.nix @@ -20,6 +20,7 @@ in system.service.bridge = { primary = mkOption { type = liminix.lib.types.serviceDefn; }; members = mkOption { type = liminix.lib.types.serviceDefn; }; + ready = mkOption { type = liminix.lib.types.serviceDefn; }; }; }; config.system.service.bridge = { @@ -28,6 +29,12 @@ in type = types.str; description = "bridge interface name to create"; }; + + macAddressFromInterface = mkOption { + type = types.nullOr liminix.lib.types.service; + default = null; + description = "reuse mac address from an existing interface service"; + }; }; members = config.system.callService ./members.nix { primary = mkOption { @@ -36,8 +43,33 @@ in }; members = mkOption { - type = types.listOf liminix.lib.types.interface; - description = "interfaces to add to the bridge"; + type = types.attrsOf (types.submodule ({ ... }: { options = { + member = mkOption { + type = liminix.lib.types.interface; + description = "interface to add"; + }; + + dependencies = mkOption { + type = types.listOf liminix.lib.types.service; + default = []; + description = "extra dependencies before attaching this interface to the bridge"; + }; + }; })); + + description = "set of bridge members"; + }; + }; + + # TODO: generalize it outside + ready = config.system.callService ./ready.nix { + primary = mkOption { + type = liminix.lib.types.service; + description = "primary bridge interface"; + }; + + members = mkOption { + type = liminix.lib.types.service; + description = "members service"; }; }; }; diff --git a/modules/bridge/members.nix b/modules/bridge/members.nix index a278730..560ba14 100644 --- a/modules/bridge/members.nix +++ b/modules/bridge/members.nix @@ -7,26 +7,22 @@ { members, primary } : let - inherit (liminix.networking) interface; - inherit (liminix.services) bundle oneshot; - inherit (lib) mkOption types; - addif = member : - # how do we get sight of services from here? maybe we need to - # implement ifwait as a regualr derivation instead of a - # servicedefinition - svc.ifwait.build { - state = "running"; - interface = member; - dependencies = [ primary member ]; - service = oneshot { - name = "${primary.name}.member.${member.name}"; - up = '' - ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) - ''; - down = "ip link set dev $(output ${member} ifname) nomaster"; - }; - }; -in bundle { + inherit (liminix.services) structuredBundle oneshot; + inherit (lib) mapAttrs; + addif = name: { dependencies ? [ ], member }: oneshot { + name = "${primary.name}.member.${name}"; + up = '' + echo "attaching $(output ${member} ifname) to $(output ${primary} ifname) bridge" + ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) + ''; + down = '' + echo "detaching $(output ${member} ifname) from any bridge" + ip link set dev $(output ${member} ifname) nomaster + ''; + + dependencies = [ primary member ] ++ dependencies; + }; +in structuredBundle { name = "${primary.name}.members"; - contents = map addif members; + contents = mapAttrs addif members; } diff --git a/modules/bridge/primary.nix b/modules/bridge/primary.nix index c25e5fe..f5e1219 100644 --- a/modules/bridge/primary.nix +++ b/modules/bridge/primary.nix @@ -3,15 +3,24 @@ , ifwait , lib }: -{ ifname } : +{ ifname, macAddressFromInterface ? null } : let inherit (liminix.services) bundle oneshot; - inherit (lib) mkOption types; + inherit (lib) mkOption types optional; in oneshot rec { name = "${ifname}.link"; up = '' - ip link add name ${ifname} type bridge - ${liminix.networking.ifup name ifname} + ${if macAddressFromInterface == null then + "ip link add name ${ifname} type bridge" + else + "ip link add name ${ifname} address $(output ${macAddressFromInterface} ether) type bridge"} + + (in_outputs ${name} + echo ${ifname} > ifname + cat /sys/class/net/${ifname}/address > ether + ) ''; - down = "ip link set down dev ${ifname}"; + down = "ip link delete ${ifname}"; + + dependencies = optional (macAddressFromInterface != null) macAddressFromInterface; } diff --git a/modules/bridge/ready.nix b/modules/bridge/ready.nix new file mode 100644 index 0000000..f8c40b3 --- /dev/null +++ b/modules/bridge/ready.nix @@ -0,0 +1,18 @@ +{ + liminix +, ifwait +, lib +}: +{ primary, members } : +let + inherit (liminix.services) oneshot; +in oneshot { + name = "${primary.name}.oper"; + up = '' + ip link set up dev $(output ${primary} ifname) + ${ifwait}/bin/ifwait -v $(output ${primary} ifname) running + ''; + down = "ip link set down dev $(output ${primary} ifname)"; + + dependencies = [ members ]; +} diff --git a/modules/hostapd/default.nix b/modules/hostapd/default.nix index 2bcf4d6..67f4f72 100644 --- a/modules/hostapd/default.nix +++ b/modules/hostapd/default.nix @@ -20,15 +20,30 @@ in { system.service.hostapd = mkOption { type = liminix.lib.types.serviceDefn; }; + + system.service.hostapd-ready = mkOption { + type = liminix.lib.types.serviceDefn; + }; }; config = { system.service.hostapd = liminix.callService ./service.nix { interface = mkOption { type = liminix.lib.types.service; }; + package = mkOption { + type = types.package; + default = pkgs.hostapd; + }; params = mkOption { type = types.attrs; }; }; + + system.service.hostapd-ready = liminix.callService ./ready.nix { + interface = mkOption { + type = liminix.lib.types.interface; + description = "Interface for which to wait that the oper state Master or Master (VLAN) has been reached."; + }; + }; }; } diff --git a/modules/hostapd/ready.nix b/modules/hostapd/ready.nix new file mode 100644 index 0000000..7c10c32 --- /dev/null +++ b/modules/hostapd/ready.nix @@ -0,0 +1,16 @@ +{ + liminix +, ifwait +, lib +}: +{ interface } : +let + inherit (liminix.services) oneshot; +in oneshot { + name = "${interface.name}.wlan-oper"; + up = '' + ${ifwait}/bin/ifbridgeable -v $(output ${interface} ifname) + ''; + + dependencies = [ interface ]; +} diff --git a/modules/hostapd/service.nix b/modules/hostapd/service.nix index 2bd5453..68e9754 100644 --- a/modules/hostapd/service.nix +++ b/modules/hostapd/service.nix @@ -1,10 +1,9 @@ { liminix -, hostapd , writeText , lib }: -{ interface, params} : +{ package, interface, params } : let inherit (liminix.services) longrun; inherit (lib) concatStringsSep mapAttrsToList; @@ -35,5 +34,5 @@ let in longrun { inherit name; dependencies = [ interface ]; - run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; + run = "${package}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; } diff --git a/modules/hostname.nix b/modules/hostname.nix index d5cd962..5f18b04 100644 --- a/modules/hostname.nix +++ b/modules/hostname.nix @@ -15,7 +15,7 @@ in { }; config = { services.hostname = oneshot { - name = "hostname"; + name = "hostname-${builtins.substring 0 12 (builtins.hashString "sha256" config.hostname)}"; up = "echo ${config.hostname} > /proc/sys/kernel/hostname"; down = "true"; }; diff --git a/modules/jitter-rng/default.nix b/modules/jitter-rng/default.nix new file mode 100644 index 0000000..aa6f9e5 --- /dev/null +++ b/modules/jitter-rng/default.nix @@ -0,0 +1,21 @@ +## CPU Jitter RNG +## ============== +## +## CPU Jitter RNG is a random number generator # providing non-physical true +## random generation # that works equally for kernel and user-land. It relies +## on the availability of a high-resolution timer. +{ lib, pkgs, ... }: +let + inherit (lib) mkOption types; + inherit (pkgs) liminix; +in { + options.system.service.jitter-rng = mkOption { + type = liminix.lib.types.serviceDefn; + }; + + config = { + system.service.jitter-rng = pkgs.liminix.callService ./jitter-rng.nix { + }; + }; +} + diff --git a/modules/jitter-rng/jitter-rng.nix b/modules/jitter-rng/jitter-rng.nix new file mode 100644 index 0000000..1072be1 --- /dev/null +++ b/modules/jitter-rng/jitter-rng.nix @@ -0,0 +1,18 @@ +{ + liminix +, lib +, jitterentropy-rngd +}: +{ }: +let + inherit (liminix.services) longrun; + name = "jitterentropy-rngd"; +in +longrun { + # Does it need to be unique? + inherit name; + run = '' + mkdir -p /run/jitterentropy-rngd + ${jitterentropy-rngd}/bin/jitterentropy-rngd -v -p /run/jitterentropy-rngd/${name}.pid + ''; +} diff --git a/modules/network/dhcpc.nix b/modules/network/dhcpc.nix index ed665b3..cd652b2 100644 --- a/modules/network/dhcpc.nix +++ b/modules/network/dhcpc.nix @@ -17,7 +17,7 @@ let ip address replace $ip/$mask dev $interface (in_outputs ${name} for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do - printenv $i > $i + (printenv $i || true) > $i done) } case $action in @@ -40,7 +40,7 @@ let ''; in longrun { inherit name; - run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; + run = "exec /bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; notification-fd = 10; dependencies = [ interface ]; } diff --git a/modules/nixpkgs.nix b/modules/nixpkgs.nix index 15c9c0c..206d506 100644 --- a/modules/nixpkgs.nix +++ b/modules/nixpkgs.nix @@ -83,11 +83,11 @@ let localSystem = cfg.hostPlatform; }; in - import ({ + import cfg.source ({ inherit (cfg) config overlays; } // systemArgs) else - import { + import cfg.source { inherit (cfg) config overlays localSystem crossSystem; }; @@ -97,6 +97,14 @@ in { options.nixpkgs = { + source = mkOption { + type = types.package // { + description = "Source of a nixpkgs repository"; + }; + + default = ; + defaultText = ""; + }; pkgs = mkOption { defaultText = literalExpression '' diff --git a/modules/outputs/initramfs.nix b/modules/outputs/initramfs.nix index 89d017f..b1e0ea8 100644 --- a/modules/outputs/initramfs.nix +++ b/modules/outputs/initramfs.nix @@ -36,22 +36,43 @@ in kernel.config = { BLK_DEV_INITRD = "y"; INITRAMFS_SOURCE = builtins.toJSON "${config.system.outputs.initramfs}"; -# INITRAMFS_COMPRESSION_LZO = "y"; + INITRAMFS_COMPRESSION_ZSTD = "y"; }; system.outputs = { initramfs = - let inherit (pkgs.pkgsBuildBuild) gen_init_cpio; + let + inherit (pkgs.pkgsBuildBuild) gen_init_cpio cpio writeScript; + inherit (pkgs) busybox; + failsafe-init = writeScript "init" '' + #!/bin/sh + exec >/dev/console + echo Running in initramfs + PATH=${busybox}/bin:$PATH + export PATH + mount -t proc none /proc + mount -t sysfs none /sys + ${busybox}/bin/sh + ''; + refs = pkgs.writeReferencesToFile busybox; in runCommand "initramfs.cpio" {} '' - cat << SPECIALS | ${gen_init_cpio}/bin/gen_init_cpio /dev/stdin > $out + cat << SPECIALS | ${gen_init_cpio}/bin/gen_init_cpio /dev/stdin > out dir /proc 0755 0 0 dir /dev 0755 0 0 nod /dev/console 0600 0 0 c 5 1 dir /target 0755 0 0 dir /target/persist 0755 0 0 dir /target/nix 0755 0 0 + dir /nix 0755 0 0 + dir /nix/store 0755 0 0 + dir /bin 0755 0 0 + file /bin/sh ${busybox}/bin/sh 0755 0 0 file /init ${pkgs.preinit}/bin/preinit 0755 0 0 + file /failsafe-init ${failsafe-init} 0755 0 0 SPECIALS + + find $(cat ${refs}) | ${cpio}/bin/cpio -H newc -o -A -v -O out + mv out $out ''; systemConfiguration = pkgs.systemconfig config.filesystem.contents; diff --git a/modules/outputs/jffs2.nix b/modules/outputs/jffs2.nix index 02c7c10..0a69a1f 100644 --- a/modules/outputs/jffs2.nix +++ b/modules/outputs/jffs2.nix @@ -5,7 +5,8 @@ , ... }: let - inherit (lib) mkIf mkOption types; + inherit (pkgs) liminix; + inherit (lib) mkIf; o = config.system.outputs; in { @@ -24,17 +25,10 @@ in }; boot.initramfs.enable = true; system.outputs = { - rootfs = - let - inherit (pkgs.pkgsBuildBuild) runCommand mtdutils; - endian = if pkgs.stdenv.isBigEndian - then "--big-endian" else "--little-endian"; - in runCommand "make-jffs2" { - depsBuildBuild = [ mtdutils ]; - } '' - tree=${o.bootablerootdir} - (cd $tree && mkfs.jffs2 --compression-mode=size ${endian} -e ${toString config.hardware.flash.eraseBlockSize} --enable-compressor=lzo --pad --root . --output $out --squash --faketime ) - ''; + rootfs = liminix.builders.jffs2 { + bootableRootDirectory = o.bootablerootdir; + inherit (config.hardware.flash) eraseBlockSize; + }; }; }; } diff --git a/modules/outputs/tftpboot-fit.its b/modules/outputs/tftpboot-fit.its new file mode 100644 index 0000000..fcb58ab --- /dev/null +++ b/modules/outputs/tftpboot-fit.its @@ -0,0 +1,14 @@ +/dts-v1/; + +/ { + description = "Liminix TFTP bootscript"; + #address-cells = <1>; + + images { + bootscript { + description = "Bootscript"; + data = /incbin/("boot.scr"); + type = "script"; + compression = "none"; + }; +}; diff --git a/modules/outputs/tftpboot.nix b/modules/outputs/tftpboot.nix index f8f0572..4fd139d 100644 --- a/modules/outputs/tftpboot.nix +++ b/modules/outputs/tftpboot.nix @@ -5,10 +5,23 @@ , ... }: let - inherit (lib) mkOption types concatStringsSep; + inherit (lib) mkOption mkIf types concatStringsSep; + inherit (pkgs) liminix; cfg = config.boot.tftp; hw = config.hardware; arch = pkgs.stdenv.hostPlatform.linuxArch; + + # UBI cannot run on the top of phram. + needsJffs2 = config.rootfsType == "ubifs"; + # squashfs doesn't work out for us because only `bootablerootdir` + # contain what we need to boot, not `config.filesystem.contents` alas. + rootfstype = if needsJffs2 then "jffs2" else config.rootfsType; + rootfs = if needsJffs2 then + liminix.builders.jffs2 { + bootableRootDirectory = config.system.outputs.bootablerootdir; + inherit (config.hardware.flash) eraseBlockSize; + } + else config.system.outputs.rootfs; in { imports = [ ../ramdisk.nix ]; options.boot.tftp = { @@ -51,11 +64,56 @@ in { It uses the Linux `phram `_ driver to emulate a flash device using a segment of physical RAM. ''; }; + + tftpboot-fit = mkOption { + type = types.package; + description = '' + tftpboot-fit + ************ + + This output is a variant that encloses the `boot.scr` in a FIT + if that's simpler to transfer for you. + ''; + }; }; config = { boot.ramdisk.enable = true; + kernel.config = mkIf needsJffs2 { + JFFS2_FS = "y"; + JFFS2_LZO = "y"; + JFFS2_RTIME = "y"; + JFFS2_COMPRESSION_OPTIONS = "y"; + JFFS2_ZLIB = "y"; + JFFS2_CMODE_SIZE = "y"; + }; + system.outputs = rec { + tftpboot-fit = + let + tftpboot-fit = pkgs.writeText "tftpboot.its" '' + /dts-v1/; + + / { + description = "Liminix TFTP bootscript"; + #address-cells = <1>; + + images { + bootscript { + description = "Bootscript"; + data = /incbin/("${tftpboot}/boot.scr"); + type = "script"; + compression = "none"; + }; + }; + }; + ''; + in + pkgs.runCommand "tftpboot-fit" { nativeBuildInputs = with pkgs.pkgsBuildBuild; [ ubootTools ]; } '' + mkdir -p $out/ + cp -rf ${tftpboot}/* $out/ + mkimage -f ${tftpboot-fit} $out/script.ub + ''; tftpboot = let inherit (pkgs.lib.trivial) toHexString; @@ -69,7 +127,7 @@ in { zimage = "bootz"; }; in choices.${cfg.kernelFormat}; - cmdline = concatStringsSep " " config.boot.commandLine; + cmdline = concatStringsSep " " config.boot.tftp.commandLine; objcopy = "${pkgs.stdenv.cc.bintools.targetPrefix}objcopy"; stripAndZip = '' ${objcopy} -O binary -R .reginfo -R .notes -R .note -R .comment -R .mdebug -R .note.gnu.build-id -S vmlinux.elf vmlinux.bin @@ -84,7 +142,7 @@ in { hex() { printf "0x%x" $1; } rootfsStart=${toString cfg.loadAddress} - rootfsSize=$(binsize64k ${o.rootfs} ) + rootfsSize=$(binsize64k ${rootfs} ) rootfsSize=$(($rootfsSize + ${toString cfg.freeSpaceBytes} )) ln -s ${o.manifest} manifest @@ -98,13 +156,13 @@ in { dtbStart=$(($rootfsStart + $rootfsSize)) ${if cfg.compressRoot then '' - lzma -z9cv ${o.rootfs} > rootfs.lz + lzma -z9cv ${rootfs} > rootfs.lz rootfsLzStart=$dtbStart rootfsLzSize=$(binsize rootfs.lz) dtbStart=$(($dtbStart + $rootfsLzSize)) '' else '' - ln -s ${o.rootfs} rootfs + ln -s ${rootfs} rootfs '' } @@ -121,7 +179,7 @@ in { fdtput -p -t s dtb /reserved-memory/$node compatible phram fdtput -p -t lx dtb /reserved-memory/$node reg $ac_prefix $(hex $rootfsStart) $sz_prefix $(hex $rootfsSize) - cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} root=/dev/mtdblock0"; + cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} rootfstype=${rootfstype} root=/dev/mtdblock0"; fdtput -t s dtb /chosen ${config.boot.commandLineDtbNode} "$cmd" dtbSize=$(binsize ./dtb ) diff --git a/modules/pki/default.nix b/modules/pki/default.nix new file mode 100644 index 0000000..b328f4d --- /dev/null +++ b/modules/pki/default.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, ... }: +# Inspired from nixpkgs/NixOS. + +with lib; + +let + inherit (pkgs.pseudofile) dir symlink; + cfg = config.security.pki; + + cacertPackage = pkgs.cacert.override { + blacklist = [ ]; + extraCertificateFiles = cfg.certificateFiles; + extraCertificateStrings = cfg.certificates; + }; + caBundleName = "ca-bundle.crt"; + caBundle = "${cacertPackage}/etc/ssl/certs/${caBundleName}"; + +in + +{ + + options = { + security.pki.installCACerts = mkEnableOption "installing CA certificates to the system" // { + default = false; + }; + + security.pki.certificateFiles = mkOption { + type = types.listOf types.path; + default = []; + example = literalExpression ''[ "''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]''; + description = '' + A list of files containing trusted root certificates in PEM + format. These are concatenated to form + {file}`/etc/ssl/certs/ca-certificates.crt`, which is + used by many programs that use OpenSSL, such as + {command}`curl` and {command}`git`. + ''; + }; + + security.pki.certificates = mkOption { + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ ''' + NixOS.org + ========= + -----BEGIN CERTIFICATE----- + MIIGUDCCBTigAwIBAgIDD8KWMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ + TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 + ... + -----END CERTIFICATE----- + ''' + ] + ''; + description = '' + A list of trusted root certificates in PEM format. + ''; + }; + }; + + config = mkIf cfg.installCACerts { + # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility. + filesystem = dir { + etc = dir { + ssl = dir { + certs = dir { + "ca-certificates.crt" = symlink caBundle; + "ca-bundle.crt" = symlink caBundle; + }; + }; + + # CentOS/Fedora compatibility. + pki = dir { + certs = dir { + "ca-bundle.crt" = symlink caBundle; + }; + }; + }; + }; + }; + +} + diff --git a/modules/s6/default.nix b/modules/s6/default.nix index cd05de7..14de5ba 100644 --- a/modules/s6/default.nix +++ b/modules/s6/default.nix @@ -1,4 +1,4 @@ -{ config, pkgs, ... }: +{ config, pkgs, lib, ... }: let inherit (pkgs) execline @@ -31,6 +31,8 @@ let mkdir $out cp -r $src $out/scripts chmod -R +w $out + substituteInPlace $out/scripts/{rc.init,rc.shutdown} \ + --replace-warn 'pkgs.seedrng' "${lib.getExe' pkgs.seedrng "seedrng"}" ''; }; service = dir { diff --git a/modules/s6/scripts/rc.init b/modules/s6/scripts/rc.init index c098ffb..59ffc8c 100755 --- a/modules/s6/scripts/rc.init +++ b/modules/s6/scripts/rc.init @@ -32,6 +32,8 @@ else mkdir -m 0751 -p /run/services/state fi +pkgs.seedrng + ### If your services are managed by s6-rc: ### (replace /run/service with your scandir) s6-rc-init -d -c /etc/s6-rc/compiled /run/service diff --git a/modules/s6/scripts/rc.shutdown b/modules/s6/scripts/rc.shutdown index 81fac67..15e88ac 100755 --- a/modules/s6/scripts/rc.shutdown +++ b/modules/s6/scripts/rc.shutdown @@ -4,6 +4,7 @@ ### Things to do before hardware halt/reboot/poweroff. ### Ideally, it should be a single call to the service manager, ### telling it to bring all the services down. +pkgs.seedrng ### If your s6-linux-init-maker invocation was made with the -1 ### option, messages from rc.shutdown will appear on /dev/console diff --git a/modules/ubus/default.nix b/modules/ubus/default.nix new file mode 100644 index 0000000..827f537 --- /dev/null +++ b/modules/ubus/default.nix @@ -0,0 +1,24 @@ +## ubus +## ==== +## +## ubus is a micro-bus à la D-Bus for all your needs. + +{ lib, pkgs, config, ...}: +let + inherit (lib) mkOption types; + inherit (pkgs) liminix; +in { + options = { + system.service.ubus = mkOption { + type = liminix.lib.types.serviceDefn; + }; + }; + config = { + system.service.ubus = liminix.callService ./service.nix { + package = mkOption { + type = types.package; + default = pkgs.ubus; + }; + }; + }; +} diff --git a/modules/ubus/service.nix b/modules/ubus/service.nix new file mode 100644 index 0000000..80548c6 --- /dev/null +++ b/modules/ubus/service.nix @@ -0,0 +1,16 @@ +{ + liminix +, writeText +, lib +}: +{ package } : +let + inherit (liminix.services) longrun; +in longrun { + # Long term: make it unique so that user can spawn multiple buses if they want. + name = "ubus"; + run = '' + mkdir -p /run/ubus + ${package}/bin/ubusd -s /run/ubus/ubus.sock + ''; +} diff --git a/overlay.nix b/overlay.nix index 09acaad..891e9a0 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,23 +1,24 @@ final: prev: let + isCross = final.stdenv.buildPlatform != final.stdenv.hostPlatform; + crossOnly = pkg : amendFn : if isCross then (amendFn pkg) else pkg; extraPkgs = import ./pkgs/default.nix { inherit (final) lib callPackage; }; inherit (final) fetchpatch; - lua_no_readline = prev.lua5_3; -# lua_no_readline = prev.lua5_3.overrideAttrs(o: { -# name = "lua-tty"; -# preBuild = '' -# makeFlagsArray+=(PLAT="posix" SYSLIBS="-Wl,-E -ldl" CFLAGS="-O2 -fPIC -DLUA_USE_POSIX -DLUA_USE_DLOPEN") -# ''; -# # lua in nixpkgs has a postInstall stanza that assumes only -# # one output, we need to override that if we're going to -# # convert to multi-output -# # outputs = ["bin" "man" "out"]; -# makeFlags = -# builtins.filter (x: (builtins.match "(PLAT|MYLIBS).*" x) == null) -# o.makeFlags; -# }); + luaHost = prev.lua5_3.overrideAttrs(o: { + name = "lua-tty"; + preBuild = '' + makeFlagsArray+=(PLAT="posix" SYSLIBS="-Wl,-E -ldl" CFLAGS="-O2 -fPIC -DLUA_USE_POSIX -DLUA_USE_DLOPEN") + ''; + # lua in nixpkgs has a postInstall stanza that assumes only + # one output, we need to override that if we're going to + # convert to multi-output + # outputs = ["bin" "man" "out"]; + makeFlags = + builtins.filter (x: (builtins.match "(PLAT|MYLIBS).*" x) == null) + o.makeFlags; + }); s6 = prev.s6.overrideAttrs(o: let @@ -42,7 +43,6 @@ let (if o ? patches then o.patches else []) ++ (if patch_needed then [ patch ] else []); }); - lua = let s = lua_no_readline.override { self = s; }; in s; in extraPkgs // { # liminix library functions @@ -130,9 +130,18 @@ extraPkgs // { "CONFIG_LIBNL32=y" "CONFIG_PKCS12=y" "CONFIG_RSN_PREAUTH=y" + "CONFIG_UBUS=y" "CONFIG_TLS=internal" ]; h = prev.hostapd.overrideAttrs(o: { + buildInputs = o.buildInputs ++ [ final.libubox final.ubus ]; + src = final.fetchFromGitea { + domain = "git.dgnum.eu"; + owner = "DGNum"; + repo = "hostapd"; + rev = "hostap-liminix-integration"; + hash = "sha256-5Xi90keCHxvuKR5Q7STuZDzuM9h9ac6aWoXVQYvqkQI="; + }; extraConfig = ""; configurePhase = '' cat > hostapd/defconfig < hostapd/defconfig < ifname + cat /sys/class/net/${ifname}/address > ether ) ''; } diff --git a/pkgs/liminix-tools/services/default.nix b/pkgs/liminix-tools/services/default.nix index c0dbaf0..466a978 100644 --- a/pkgs/liminix-tools/services/default.nix +++ b/pkgs/liminix-tools/services/default.nix @@ -39,6 +39,7 @@ let , contents ? [] , buildInputs ? [] , isTrigger ? false + , passthru ? {} } @ args: stdenvNoCC.mkDerivation { # we use stdenvNoCC to avoid generating derivations with names @@ -50,6 +51,8 @@ let dependencies = builtins.map (d: d.name) dependencies; contents = builtins.map (d: d.name) contents; builder = ./builder.sh; + + inherit passthru; }; longrun = { @@ -100,7 +103,18 @@ let serviceType = "bundle"; inherit contents dependencies; }); + structuredBundle = { + name + , contents ? {} + , dependencies ? [] + , ... + } @ args: service (args // { + serviceType = "bundle"; + contents = builtins.attrValues contents; + inherit dependencies; + passthru.components = contents; + }); target = bundle; in { - inherit target bundle oneshot longrun output; + inherit target bundle oneshot longrun output structuredBundle; } diff --git a/pkgs/min-copy-closure/liminix-rebuild.sh b/pkgs/min-copy-closure/liminix-rebuild.sh index 16ec7f1..18f57f0 100755 --- a/pkgs/min-copy-closure/liminix-rebuild.sh +++ b/pkgs/min-copy-closure/liminix-rebuild.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -Eeuo pipefail ssh_command=${SSH_COMMAND-ssh} @@ -13,19 +14,24 @@ case "$1" in reboot="soft" shift ;; + "--root") + root_prefix="$2" + shift + shift + ;; esac target_host=$1 shift if [ -z "$target_host" ] ; then - echo Usage: liminix-rebuild \[--no-reboot\] target-host params + echo Usage: liminix-rebuild \[--no-reboot\] \[--fast\] target-host params exit 1 fi if toplevel=$(nix-build "$@" -A outputs.systemConfiguration --no-out-link); then echo systemConfiguration $toplevel - min-copy-closure $target_host $toplevel + min-copy-closure --root "$root_prefix" $target_host $toplevel $ssh_command $target_host $toplevel/bin/install case "$reboot" in reboot) diff --git a/pkgs/preinit/parseopts.c b/pkgs/preinit/parseopts.c index 1803341..243f3b0 100644 --- a/pkgs/preinit/parseopts.c +++ b/pkgs/preinit/parseopts.c @@ -65,13 +65,18 @@ static char * eat_param(char *p, char *param_name, char **out) return p; } +#define SCAN_CMDLINE(cmdline, identifier, field) do { \ + for (char* p = strdup(cmdline); *p; p++) { \ + p = eat_param(p, identifier, &(opts->field)); \ + } \ +} while(0) + + void parseopts(char * cmdline, struct root_opts *opts) { - for(char *p = cmdline; *p; p++) { - p = eat_param(p, "root=", &(opts->device)); - p = eat_param(p, "rootfstype=", &(opts->fstype)); - p = eat_param(p, "rootflags=", &(opts->mount_opts)); - p = eat_param(p, "altroot=", &(opts->altdevice)); - }; + SCAN_CMDLINE(cmdline, "root=", device); + SCAN_CMDLINE(cmdline, "rootfstype=", fstype); + SCAN_CMDLINE(cmdline, "rootflags=", mount_opts); + SCAN_CMDLINE(cmdline, "rootalt=", altdevice); } #ifdef TESTS @@ -85,6 +90,8 @@ void parseopts(char * cmdline, struct root_opts *opts) { #define S(x) #x #define expect_equal(actual, expected) \ if(!actual || strcmp(actual, expected)) die("%d: expected \"%s\", got \"%s\"", __LINE__, expected, actual); +#define expect_null(actual) \ + if (actual) die("%d: expected null, got \"%s\"", __LINE__, actual); int main() @@ -92,6 +99,7 @@ int main() struct root_opts opts = { .device = "/dev/hda1", .fstype = "xiafs", + .altdevice = NULL, .mount_opts = NULL }; char *buf; @@ -103,14 +111,22 @@ int main() expect_equal(opts.fstype, "ubifs"); expect_equal(opts.mount_opts, "subvol=1"); - // finds altroot= options - buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 altroot=/dev/mtdblock6 foo"); + // finds rootalt= options + buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 rootalt=/dev/mtdblock6 foo"); memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); expect_equal(opts.device, "/dev/mtdblock0"); expect_equal(opts.altdevice, "/dev/mtdblock6"); expect_equal(opts.fstype, "ubifs"); expect_equal(opts.mount_opts, "subvol=1"); + // Ensure that `altdevice` is NULL. + buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 fw_devlink=off rootfstype=ubifs mtdparts=phram0:19791872(rootfs) phram.phram=phram0,33554432,19791872,65536 rootfstype=jffs2 root=/dev/mtdblock0"); + memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); + expect_equal(opts.device, "/dev/mtdblock0"); + expect_null(opts.altdevice); + expect_equal(opts.fstype, "jffs2"); + expect_null(opts.mount_opts); + // in case of duplicates, chooses the latter // also: works if the option is end of string buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0"); @@ -146,12 +162,12 @@ int main() if(opts.altdevice) die("expected null altdevice, got \"%s\"", opts.altdevice); // provides empty strings for empty options - buf = strdup("liminix rootfstype= fw_devlink=off root= altroot= /dev/hda1"); + buf = strdup("liminix rootfstype= fw_devlink=off root= rootalt= /dev/hda1"); memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); if(strlen(opts.fstype)) die("expected empty rootfstype, got \"%s\"", opts.fstype); if(strlen(opts.device)) die("expected empty root, got \"%s\"", opts.device); - if(strlen(opts.altdevice)) die("expected empty altroot, got \"%s\"", opts.altdevice); + if(strlen(opts.altdevice)) die("expected empty rootalt, got \"%s\"", opts.altdevice); expect_equal("01", pr_u32(1)); expect_equal("ab", pr_u32(0xab)); diff --git a/pkgs/preinit/preinit.c b/pkgs/preinit/preinit.c index 7dd896b..c9a1821 100644 --- a/pkgs/preinit/preinit.c +++ b/pkgs/preinit/preinit.c @@ -4,9 +4,13 @@ #include #include #include +#include #include #include #include +#include + +#include #include /* for COMMAND_LINE_SIZE */ @@ -44,6 +48,25 @@ static int fork_exec(char * command, char *args[]) return execve(command, args, NULL); } +static void debug_listdir(const char * path) +{ + DIR *mydir; + struct dirent *myfile; + struct stat mystat; + + char buf[512]; + mydir = opendir(path); + while((myfile = readdir(mydir)) != NULL) + { + sprintf(buf, "%s/%s", path, myfile->d_name); + stat(buf, &mystat); + printf("%llu", mystat.st_size); + printf(" %s\n", myfile->d_name); + } + closedir(mydir); + +} + char banner[] = "Running pre-init...\n"; char buf[COMMAND_LINE_SIZE]; @@ -98,6 +121,7 @@ int main(int argc, char *argv[], char *envp[]) AVER(mount(opts.device, "/target/persist", opts.fstype, 0, opts.mount_opts)); } else { if(mount(opts.device, "/target/persist", opts.fstype, 0, opts.mount_opts) < 0) { + ERR("failed to mount primary device, mount the alternative device\n"); AVER(mount(opts.altdevice, "/target/persist", opts.fstype, 0, opts.mount_opts)); } } @@ -107,7 +131,12 @@ int main(int argc, char *argv[], char *envp[]) "bind", MS_BIND, NULL)); char *exec_args[] = { "activate", "/target", NULL }; - AVER(fork_exec("/target/persist/activate", exec_args)); + if (fork_exec("/target/persist/activate", exec_args) < 0) { + ERR("failed to activate the system\n"); + pr_u32(errno); ERR ( " - "); ERR(strerror(errno)); ERR("\n"); + goto failsafe; + } + AVER(chdir("/target")); AVER(mount("/target", "/", "bind", MS_BIND | MS_REC, NULL)); @@ -118,5 +147,23 @@ int main(int argc, char *argv[], char *envp[]) AVER(execve("/persist/init", argv, envp)); } + +failsafe: + debug_listdir("/"); + debug_listdir("/target"); + + ERR("failed to mount the rootfs\n"); + ERR("final stand using the failsafe initialization method\n"); + ERR("the boot process is manual from now on\n"); + + argv[0] = "init"; + argv[1] = NULL; + // Attempt to unmount the /target mount-bind. + AVER(umount("/target")); + AVER(execve("/failsafe-init", argv, envp)); + + debug_listdir("/"); + debug_listdir("/target"); + die(); } diff --git a/pkgs/run-liminix-vm/default.nix b/pkgs/run-liminix-vm/default.nix index 1bf0389..f3df1e1 100644 --- a/pkgs/run-liminix-vm/default.nix +++ b/pkgs/run-liminix-vm/default.nix @@ -2,14 +2,15 @@ qemuLim , socat , writeShellScript -, writeFennel , runCommand +, fennel , lib , lua , pkgsBuildBuild }: let - run-liminix-vm = pkgsBuildBuild.writeFennel "run-liminix-vm" { - packages = [ qemuLim pkgsBuildBuild.lua.pkgs.luaposix pkgsBuildBuild.lua.pkgs.fennel ]; + writeFennel = pkgsBuildBuild.writeFennel.override { inherit lua; }; + run-liminix-vm = writeFennel "run-liminix-vm" { + packages = [ qemuLim lua.pkgs.luaposix fennel ]; } ./run-liminix-vm.fnl; connect = writeShellScript "connect-vm" '' export PATH="${lib.makeBinPath [socat]}:$PATH" diff --git a/pkgs/seedrng/COPYING b/pkgs/seedrng/COPYING new file mode 100644 index 0000000..0c5e554 --- /dev/null +++ b/pkgs/seedrng/COPYING @@ -0,0 +1,712 @@ +This project is licensed under any one of: + +- GPL-2.0 +- Apache-2.0 +- MIT +- BSD-1-Clause +- CC0-1.0 + +Choose which one works best for you. They are reproduced below. + +------------------------------------------------------------------------------- + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. +------------------------------------------------------------------------------- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +------------------------------------------------------------------------------- +MIT License + +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. +------------------------------------------------------------------------------- +BSD 1-Clause License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- +Creative Commons Legal Code - CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. +------------------------------------------------------------------------------- diff --git a/pkgs/seedrng/Makefile b/pkgs/seedrng/Makefile new file mode 100644 index 0000000..35e783c --- /dev/null +++ b/pkgs/seedrng/Makefile @@ -0,0 +1,18 @@ +PREFIX ?= /usr +DESTDIR ?= +SBINDIR ?= $(PREFIX)/sbin +LOCALSTATEDIR ?= /var/lib +CFLAGS ?= -O2 -pipe + +CFLAGS += -Wall -Wextra -pedantic +CFLAGS += -DLOCALSTATEDIR="\"$(LOCALSTATEDIR)\"" + +seedrng: seedrng.c + +install: seedrng + install -v -d "$(DESTDIR)$(SBINDIR)" && install -v -m 0755 seedrng "$(DESTDIR)$(SBINDIR)/seedrng" + +clean: + rm -f seedrng + +.PHONY: clean diff --git a/pkgs/seedrng/README.md b/pkgs/seedrng/README.md new file mode 100644 index 0000000..a8ceacd --- /dev/null +++ b/pkgs/seedrng/README.md @@ -0,0 +1,72 @@ +## SeedRNG — `seedrng(8)` +##### by [Jason A. Donenfeld](mailto:Jason@zx2c4.com) + +SeedRNG is a simple program made for seeding the Linux kernel random number +generator from seed files. The program takes no arguments, must be run as root, +and always attempts to do something useful. + +This program is useful in light of the fact that the Linux kernel RNG cannot be +initialized from shell scripts, and new seeds cannot be safely generated from +boot time shell scripts either. + +It should be run once at init time and once at shutdown time. It can be run at +other times without detriment as well. Whenever it is run, it writes existing +seed files into the RNG pool, and then creates a new seed file. If the RNG is +initialized at the time of creating a new seed file, then that new seed file is +marked as "creditable", which means it can be used to initialize the RNG. +Otherwise, it is marked as "non-creditable", in which case it is still used to +seed the RNG's pool, but will not initialize the RNG. + +In order to ensure that entropy only ever stays the same or increases from one +seed file to the next, old seed values are hashed together with new seed values +when writing new seed files: + +``` +new_seed = new_seed[:-32] || HASH(fixed_prefix || real_time || boot_time || old_seed_len || old_seed || new_seed_len || new_seed) +``` + +The seed is stored in `LOCALSTATEDIR/seedrng/`, which can be adjusted at +compile time. If the `SEEDRNG_SKIP_CREDIT` environment variable is set to `1`, +`true`, `yes`, or `y`, then seeds never credit the RNG, even if the seed file +is creditable. + +Being a single C file, `seedrng.c`, SeedRNG is meant to be copy and pasted +verbatim into various minimal init system projects and tweaked as needed. +**Please do not package this repo as a standalone program**: it is intended as +utility code meant to be imported into existing projects instead. + +### Building & Installing + +``` +$ make +$ sudo make install +``` + +In addition to the usual compiler environment variables (`CFLAGS`, etc), the +following environment variable is respected during compilation: + + * `LOCALSTATEDIR` default: `/var/lib` + +The following environment variables are respected during installation: + + * `PREFIX` default: `/usr` + * `DESTDIR` default: + * `SBINDIR` default: `$(PREFIX)/sbin` + +### Usage + +``` +# seedrng +``` + +However, this invocation should generally come from init and shutdown scripts. + +### License + +This program is licensed under any one of the the following licenses, so that it can be incorporated into other software projects as needed: + + - GPL-2.0 + - Apache-2.0 + - MIT + - BSD-1-Clause + - CC0-1.0 diff --git a/pkgs/seedrng/default.nix b/pkgs/seedrng/default.nix new file mode 100644 index 0000000..973ed4a --- /dev/null +++ b/pkgs/seedrng/default.nix @@ -0,0 +1,13 @@ +{ stdenv }: +stdenv.mkDerivation { + pname = "seedrng"; + version = "2022.04"; + + src = ./.; + + makeFlags = [ + "PREFIX=${placeholder "out"}" + "SBINDIR=${placeholder "out"}/bin" + "LOCALSTATEDIR=/persist" + ]; +} diff --git a/pkgs/seedrng/seedrng.c b/pkgs/seedrng/seedrng.c new file mode 100644 index 0000000..9f93fe2 --- /dev/null +++ b/pkgs/seedrng/seedrng.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: (GPL-2.0 OR Apache-2.0 OR MIT OR BSD-1-Clause OR CC0-1.0) +/* + * Copyright (C) 2022 Jason A. Donenfeld . All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef LOCALSTATEDIR +#define LOCALSTATEDIR "/var/lib" +#endif + +#define SEED_DIR LOCALSTATEDIR "/seedrng" +#define CREDITABLE_SEED "seed.credit" +#define NON_CREDITABLE_SEED "seed.no-credit" + +enum blake2s_lengths { + BLAKE2S_BLOCK_LEN = 64, + BLAKE2S_HASH_LEN = 32, + BLAKE2S_KEY_LEN = 32 +}; + +enum seedrng_lengths { + MAX_SEED_LEN = 512, + MIN_SEED_LEN = BLAKE2S_HASH_LEN +}; + +struct blake2s_state { + uint32_t h[8]; + uint32_t t[2]; + uint32_t f[2]; + uint8_t buf[BLAKE2S_BLOCK_LEN]; + unsigned int buflen; + unsigned int outlen; +}; + +#define le32_to_cpup(a) le32toh(*(a)) +#define cpu_to_le32(a) htole32(a) +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#endif + +static inline void cpu_to_le32_array(uint32_t *buf, unsigned int words) +{ + while (words--) { + *buf = cpu_to_le32(*buf); + ++buf; + } +} + +static inline void le32_to_cpu_array(uint32_t *buf, unsigned int words) +{ + while (words--) { + *buf = le32_to_cpup(buf); + ++buf; + } +} + +static inline uint32_t ror32(uint32_t word, unsigned int shift) +{ + return (word >> (shift & 31)) | (word << ((-shift) & 31)); +} + +static const uint32_t blake2s_iv[8] = { + 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, + 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL +}; + +static const uint8_t blake2s_sigma[10][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, + { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, + { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, + { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, + { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, + { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, +}; + +static void blake2s_set_lastblock(struct blake2s_state *state) +{ + state->f[0] = -1; +} + +static void blake2s_increment_counter(struct blake2s_state *state, const uint32_t inc) +{ + state->t[0] += inc; + state->t[1] += (state->t[0] < inc); +} + +static void blake2s_init_param(struct blake2s_state *state, const uint32_t param) +{ + int i; + + memset(state, 0, sizeof(*state)); + for (i = 0; i < 8; ++i) + state->h[i] = blake2s_iv[i]; + state->h[0] ^= param; +} + +static void blake2s_init(struct blake2s_state *state, const size_t outlen) +{ + blake2s_init_param(state, 0x01010000 | outlen); + state->outlen = outlen; +} + +static void blake2s_compress(struct blake2s_state *state, const uint8_t *block, size_t nblocks, const uint32_t inc) +{ + uint32_t m[16]; + uint32_t v[16]; + int i; + + while (nblocks > 0) { + blake2s_increment_counter(state, inc); + memcpy(m, block, BLAKE2S_BLOCK_LEN); + le32_to_cpu_array(m, ARRAY_SIZE(m)); + memcpy(v, state->h, 32); + v[ 8] = blake2s_iv[0]; + v[ 9] = blake2s_iv[1]; + v[10] = blake2s_iv[2]; + v[11] = blake2s_iv[3]; + v[12] = blake2s_iv[4] ^ state->t[0]; + v[13] = blake2s_iv[5] ^ state->t[1]; + v[14] = blake2s_iv[6] ^ state->f[0]; + v[15] = blake2s_iv[7] ^ state->f[1]; + +#define G(r, i, a, b, c, d) do { \ + a += b + m[blake2s_sigma[r][2 * i + 0]]; \ + d = ror32(d ^ a, 16); \ + c += d; \ + b = ror32(b ^ c, 12); \ + a += b + m[blake2s_sigma[r][2 * i + 1]]; \ + d = ror32(d ^ a, 8); \ + c += d; \ + b = ror32(b ^ c, 7); \ +} while (0) + +#define ROUND(r) do { \ + G(r, 0, v[0], v[ 4], v[ 8], v[12]); \ + G(r, 1, v[1], v[ 5], v[ 9], v[13]); \ + G(r, 2, v[2], v[ 6], v[10], v[14]); \ + G(r, 3, v[3], v[ 7], v[11], v[15]); \ + G(r, 4, v[0], v[ 5], v[10], v[15]); \ + G(r, 5, v[1], v[ 6], v[11], v[12]); \ + G(r, 6, v[2], v[ 7], v[ 8], v[13]); \ + G(r, 7, v[3], v[ 4], v[ 9], v[14]); \ +} while (0) + ROUND(0); + ROUND(1); + ROUND(2); + ROUND(3); + ROUND(4); + ROUND(5); + ROUND(6); + ROUND(7); + ROUND(8); + ROUND(9); + +#undef G +#undef ROUND + + for (i = 0; i < 8; ++i) + state->h[i] ^= v[i] ^ v[i + 8]; + + block += BLAKE2S_BLOCK_LEN; + --nblocks; + } +} + +static void blake2s_update(struct blake2s_state *state, const void *inp, size_t inlen) +{ + const size_t fill = BLAKE2S_BLOCK_LEN - state->buflen; + const uint8_t *in = inp; + + if (!inlen) + return; + if (inlen > fill) { + memcpy(state->buf + state->buflen, in, fill); + blake2s_compress(state, state->buf, 1, BLAKE2S_BLOCK_LEN); + state->buflen = 0; + in += fill; + inlen -= fill; + } + if (inlen > BLAKE2S_BLOCK_LEN) { + const size_t nblocks = DIV_ROUND_UP(inlen, BLAKE2S_BLOCK_LEN); + blake2s_compress(state, in, nblocks - 1, BLAKE2S_BLOCK_LEN); + in += BLAKE2S_BLOCK_LEN * (nblocks - 1); + inlen -= BLAKE2S_BLOCK_LEN * (nblocks - 1); + } + memcpy(state->buf + state->buflen, in, inlen); + state->buflen += inlen; +} + +static void blake2s_final(struct blake2s_state *state, uint8_t *out) +{ + blake2s_set_lastblock(state); + memset(state->buf + state->buflen, 0, BLAKE2S_BLOCK_LEN - state->buflen); + blake2s_compress(state, state->buf, 1, state->buflen); + cpu_to_le32_array(state->h, ARRAY_SIZE(state->h)); + memcpy(out, state->h, state->outlen); +} + +static ssize_t getrandom_full(void *buf, size_t count, unsigned int flags) +{ + ssize_t ret, total = 0; + uint8_t *p = buf; + + do { + ret = getrandom(p, count, flags); + if (ret < 0 && errno == EINTR) + continue; + else if (ret < 0) + return ret; + total += ret; + p += ret; + count -= ret; + } while (count); + return total; +} + +static ssize_t read_full(int fd, void *buf, size_t count) +{ + ssize_t ret, total = 0; + uint8_t *p = buf; + + do { + ret = read(fd, p, count); + if (ret < 0 && errno == EINTR) + continue; + else if (ret < 0) + return ret; + else if (ret == 0) + break; + total += ret; + p += ret; + count -= ret; + } while (count); + return total; +} + +static ssize_t write_full(int fd, const void *buf, size_t count) +{ + ssize_t ret, total = 0; + const uint8_t *p = buf; + + do { + ret = write(fd, p, count); + if (ret < 0 && errno == EINTR) + continue; + else if (ret < 0) + return ret; + total += ret; + p += ret; + count -= ret; + } while (count); + return total; +} + +static size_t determine_optimal_seed_len(void) +{ + size_t ret = 0; + char poolsize_str[11] = { 0 }; + int fd = open("/proc/sys/kernel/random/poolsize", O_RDONLY); + + if (fd < 0 || read_full(fd, poolsize_str, sizeof(poolsize_str) - 1) < 0) { + perror("Unable to determine pool size, falling back to 256 bits"); + ret = MIN_SEED_LEN; + } else + ret = DIV_ROUND_UP(strtoul(poolsize_str, NULL, 10), 8); + if (fd >= 0) + close(fd); + if (ret < MIN_SEED_LEN) + ret = MIN_SEED_LEN; + else if (ret > MAX_SEED_LEN) + ret = MAX_SEED_LEN; + return ret; +} + +static int read_new_seed(uint8_t *seed, size_t len, bool *is_creditable) +{ + ssize_t ret; + int urandom_fd; + + *is_creditable = false; + ret = getrandom_full(seed, len, GRND_NONBLOCK); + if (ret == (ssize_t)len) { + *is_creditable = true; + return 0; + } else if (ret < 0 && errno == ENOSYS) { + struct pollfd random_fd = { + .fd = open("/dev/random", O_RDONLY), + .events = POLLIN + }; + if (random_fd.fd < 0) + return -errno; + *is_creditable = poll(&random_fd, 1, 0) == 1; + close(random_fd.fd); + } else if (getrandom_full(seed, len, GRND_INSECURE) == (ssize_t)len) + return 0; + urandom_fd = open("/dev/urandom", O_RDONLY); + if (urandom_fd < 0) + return -1; + ret = read_full(urandom_fd, seed, len); + if (ret == (ssize_t)len) + ret = 0; + else + ret = -errno ? -errno : -EIO; + close(urandom_fd); + errno = -ret; + return ret ? -1 : 0; +} + +static int seed_rng(uint8_t *seed, size_t len, bool credit) +{ + struct { + int entropy_count; + int buf_size; + uint8_t buffer[MAX_SEED_LEN]; + } req = { + .entropy_count = credit ? len * 8 : 0, + .buf_size = len + }; + int random_fd, ret; + + if (len > sizeof(req.buffer)) { + errno = EFBIG; + return -1; + } + memcpy(req.buffer, seed, len); + + random_fd = open("/dev/urandom", O_RDONLY); + if (random_fd < 0) + return -1; + ret = ioctl(random_fd, RNDADDENTROPY, &req); + if (ret) + ret = -errno ? -errno : -EIO; + close(random_fd); + errno = -ret; + return ret ? -1 : 0; +} + +static int seed_from_file_if_exists(const char *filename, int dfd, bool credit, struct blake2s_state *hash) +{ + uint8_t seed[MAX_SEED_LEN]; + ssize_t seed_len; + int fd = -1, ret = 0; + + fd = openat(dfd, filename, O_RDONLY); + if (fd < 0 && errno == ENOENT) + return 0; + else if (fd < 0) { + ret = -errno; + perror("Unable to open seed file"); + goto out; + } + seed_len = read_full(fd, seed, sizeof(seed)); + if (seed_len < 0) { + ret = -errno; + perror("Unable to read seed file"); + goto out; + } + if ((unlinkat(dfd, filename, 0) < 0 || fsync(dfd) < 0) && seed_len) { + ret = -errno; + perror("Unable to remove seed after reading, so not seeding"); + goto out; + } + if (!seed_len) + goto out; + + blake2s_update(hash, &seed_len, sizeof(seed_len)); + blake2s_update(hash, seed, seed_len); + + printf("Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without"); + if (seed_rng(seed, seed_len, credit) < 0) { + ret = -errno; + perror("Unable to seed"); + } + +out: + if (fd >= 0) + close(fd); + errno = -ret; + return ret ? -1 : 0; +} + +static bool skip_credit(void) +{ + const char *skip = getenv("SEEDRNG_SKIP_CREDIT"); + return skip && (!strcmp(skip, "1") || !strcasecmp(skip, "true") || + !strcasecmp(skip, "yes") || !strcasecmp(skip, "y")); +} + +int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) +{ + static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix"; + static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure"; + int fd = -1, dfd = -1, program_ret = 0; + uint8_t new_seed[MAX_SEED_LEN]; + size_t new_seed_len; + bool new_seed_creditable; + struct timespec realtime = { 0 }, boottime = { 0 }; + struct blake2s_state hash; + + umask(0077); + if (getuid()) { + errno = EACCES; + perror("This program requires root"); + return 1; + } + + blake2s_init(&hash, BLAKE2S_HASH_LEN); + blake2s_update(&hash, seedrng_prefix, strlen(seedrng_prefix)); + clock_gettime(CLOCK_REALTIME, &realtime); + clock_gettime(CLOCK_BOOTTIME, &boottime); + blake2s_update(&hash, &realtime, sizeof(realtime)); + blake2s_update(&hash, &boottime, sizeof(boottime)); + + if (mkdir(SEED_DIR, 0700) < 0 && errno != EEXIST) { + perror("Unable to create seed directory"); + return 1; + } + + dfd = open(SEED_DIR, O_DIRECTORY | O_RDONLY); + if (dfd < 0 || flock(dfd, LOCK_EX) < 0) { + perror("Unable to lock seed directory"); + program_ret = 1; + goto out; + } + + if (seed_from_file_if_exists(NON_CREDITABLE_SEED, dfd, false, &hash) < 0) + program_ret |= 1 << 1; + if (seed_from_file_if_exists(CREDITABLE_SEED, dfd, !skip_credit(), &hash) < 0) + program_ret |= 1 << 2; + + new_seed_len = determine_optimal_seed_len(); + if (read_new_seed(new_seed, new_seed_len, &new_seed_creditable) < 0) { + perror("Unable to read new seed"); + new_seed_len = BLAKE2S_HASH_LEN; + strncpy((char *)new_seed, seedrng_failure, new_seed_len); + program_ret |= 1 << 3; + } + blake2s_update(&hash, &new_seed_len, sizeof(new_seed_len)); + blake2s_update(&hash, new_seed, new_seed_len); + blake2s_final(&hash, new_seed + new_seed_len - BLAKE2S_HASH_LEN); + + printf("Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable"); + fd = openat(dfd, NON_CREDITABLE_SEED, O_WRONLY | O_CREAT | O_TRUNC, 0400); + if (fd < 0) { + perror("Unable to open seed file for writing"); + program_ret |= 1 << 4; + goto out; + } + if (write_full(fd, new_seed, new_seed_len) != (ssize_t)new_seed_len || fsync(fd) < 0) { + perror("Unable to write seed file"); + program_ret |= 1 << 5; + goto out; + } + if (new_seed_creditable && renameat(dfd, NON_CREDITABLE_SEED, dfd, CREDITABLE_SEED) < 0) { + perror("Unable to make new seed creditable"); + program_ret |= 1 << 6; + } +out: + if (fd >= 0) + close(fd); + if (dfd >= 0) + close(dfd); + return program_ret; +} diff --git a/pkgs/ubus/default.nix b/pkgs/ubus/default.nix new file mode 100644 index 0000000..fa063bf --- /dev/null +++ b/pkgs/ubus/default.nix @@ -0,0 +1,35 @@ +{ stdenv, fetchFromGitea, lib, cmake, libubox, json_c, lua, defaultSocketLocation ? "/run/ubus/ubus.sock" }: +stdenv.mkDerivation { + pname = "ubus"; + version = "unstable-04-09-2024"; + + src = fetchFromGitea { + domain = "git.dgnum.eu"; + owner = "DGNum"; + repo = "ubus"; + rev = "ebb1dc92e4985538a8e18b7e926264118138f281"; + hash = "sha256-fo4zleC9R6uzlcOJ/jQ0t0nSBHUAq/uqPVd9xJdkAM0="; + }; + + # We don't use /var/run/ in Liminix by default. + postPatch = '' + substituteInPlace CMakeLists.txt \ + --replace-fail "/var/run/ubus/ubus.sock" "${defaultSocketLocation}" + ''; + + nativeBuildInputs = [ + cmake + ]; + + buildInputs = [ + lua + libubox + json_c + ]; + + cmakeFlags = [ + "-DBUILD_LUA=on" + "-DLUAPATH=${placeholder "out"}/lib/lua/${lua.luaversion}" + "-DBUILD_EXAMPLES=off" + ]; +} diff --git a/pkgs/write-fennel/default.nix b/pkgs/write-fennel/default.nix index 67d1908..e72762d 100644 --- a/pkgs/write-fennel/default.nix +++ b/pkgs/write-fennel/default.nix @@ -27,7 +27,7 @@ name : echo "#!${lua}/bin/lua ${luaFlags}" echo "package.path = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luapath)} .. package.path" echo "package.cpath = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luacpath)} .. package.cpath" - echo "local ok, stdlib = pcall(require,'posix.stdlib'); if ok then stdlib.setenv('PATH',${lib.escapeShellArg (lib.makeBinPath packages)} .. \":\" .. os.getenv('PATH')) end" + echo "local ok, stdlib = pcall(require,'posix.stdlib'); if ok then stdlib.setenv('PATH', \"${lib.makeBinPath packages}\" .. \":\" .. os.getenv('PATH')) end" fennel ${if correlate then "--correlate" else ""} --compile ${source} ) > ${name}.lua ''; diff --git a/tests/ext4/test.nix b/tests/ext4/test.nix index f972583..5c5a582 100644 --- a/tests/ext4/test.nix +++ b/tests/ext4/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/fennel/test.nix b/tests/fennel/test.nix index fda2a89..81206d9 100644 --- a/tests/fennel/test.nix +++ b/tests/fennel/test.nix @@ -4,7 +4,7 @@ }: let overlay = import "${liminix}/overlay.nix"; - pkgs = import { overlays = [overlay]; }; + pkgs = import nixpkgs { overlays = [overlay]; }; script = pkgs.writeFennelScript "foo" [] ./hello.fnl; inherit (pkgs.lua.pkgs) fifo; netlink = pkgs.netlink-lua; diff --git a/tests/inout/test.nix b/tests/inout/test.nix index 5f382e4..3f3b198 100644 --- a/tests/inout/test.nix +++ b/tests/inout/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/jffs2/configuration.nix b/tests/jffs2/configuration.nix index 2515fbf..ea7670b 100644 --- a/tests/jffs2/configuration.nix +++ b/tests/jffs2/configuration.nix @@ -5,7 +5,6 @@ in { imports = [ ../../vanilla-configuration.nix ../../modules/squashfs.nix - ../../modules/outputs/jffs2.nix ]; config.rootfsType = "jffs2"; config.filesystem = dir { diff --git a/tests/jffs2/test.nix b/tests/jffs2/test.nix index f972583..5c5a582 100644 --- a/tests/jffs2/test.nix +++ b/tests/jffs2/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/min-copy-closure/configuration.nix b/tests/min-copy-closure/configuration.nix index fe80bf2..0797341 100644 --- a/tests/min-copy-closure/configuration.nix +++ b/tests/min-copy-closure/configuration.nix @@ -13,7 +13,6 @@ let in { imports = [ ../../vanilla-configuration.nix - ../../modules/outputs/jffs2.nix ]; config = { services.sshd = longrun { diff --git a/tests/min-copy-closure/test.nix b/tests/min-copy-closure/test.nix index 14f0225..774b882 100644 --- a/tests/min-copy-closure/test.nix +++ b/tests/min-copy-closure/test.nix @@ -8,7 +8,7 @@ let lmx = (import liminix { }); rogue = lmx.pkgs.rogue; img = lmx.outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/pppoe/test.nix b/tests/pppoe/test.nix index c8007a9..6972256 100644 --- a/tests/pppoe/test.nix +++ b/tests/pppoe/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu"; liminix-config = ./configuration.nix; }).outputs.default; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; inherit (pkgs.pkgsBuildBuild) routeros; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ diff --git a/tests/tftpboot/test.nix b/tests/tftpboot/test.nix index b7a3e87..b6b9f1a 100644 --- a/tests/tftpboot/test.nix +++ b/tests/tftpboot/test.nix @@ -1,5 +1,6 @@ { - liminix + liminix, + ... }: let check = deviceName : config : let derivation = (import liminix { diff --git a/tests/updown/test.nix b/tests/updown/test.nix index 589e383..45f7c91 100644 --- a/tests/updown/test.nix +++ b/tests/updown/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/wlan/configuration.nix b/tests/wlan/configuration.nix index 6dc4b44..ed1d948 100644 --- a/tests/wlan/configuration.nix +++ b/tests/wlan/configuration.nix @@ -7,6 +7,7 @@ in rec { ../../modules/wlan.nix ../../modules/hostapd ../../modules/network + ./wpa_supplicant.nix ]; services.hostap = config.system.service.hostapd.build { @@ -27,5 +28,21 @@ in rec { }; }; - defaultProfile.packages = with pkgs; [ tcpdump ] ; + services.wpa_supplicant = config.system.service.wpa_supplicant.build { + interface = "wlan1"; + driver = "nl80211"; + config-file = pkgs.writeText "wpa_supplicant.conf" '' + country=us + update_config=1 + ctrl_interface=/run/wpa_supplicant + + network={ + scan_ssid=1 + ssid="liminix" + psk="colourless green ideas" + } + ''; + }; + + defaultProfile.packages = with pkgs; [ tcpdump wpa_supplicant ]; } diff --git a/tests/wlan/test.nix b/tests/wlan/test.nix index fcc2715..f1f20c2 100644 --- a/tests/wlan/test.nix +++ b/tests/wlan/test.nix @@ -3,10 +3,11 @@ , nixpkgs }: let img = (import liminix { - device = import "${liminix}/devices/qemu-armv7l/"; + inherit nixpkgs; + device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.default; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect socat diff --git a/tests/wlan/wait-for-wlan.expect b/tests/wlan/wait-for-wlan.expect index 1d42a4e..5e0f073 100644 --- a/tests/wlan/wait-for-wlan.expect +++ b/tests/wlan/wait-for-wlan.expect @@ -14,10 +14,10 @@ expect { } expect "#" while { $FINISHED < 10 } { - send "date && grep AP-ENABLED /run/uncaught-logs/* || echo \$NOT\r\n" + send "date && grep CTRL-EVENT-CONNECTED /run/uncaught-logs/* || echo \$NOT\r\n" expect { - "wlan0: AP-ENABLED" { set FINISHED 999; set EXIT 0; } + "wlan1: CTRL-EVENT-CONNECTED" { set FINISHED 999; set EXIT 0; } "not_present" { send_user "waiting ...\n" ; sleep 5 } } set FINISHED [ expr $FINISHED + 1 ] diff --git a/tests/wlan/wpa_service.nix b/tests/wlan/wpa_service.nix new file mode 100644 index 0000000..ae70db3 --- /dev/null +++ b/tests/wlan/wpa_service.nix @@ -0,0 +1,21 @@ +{ + liminix, + wpa_supplicant, + lib, +}: +{ + interface, + driver, + config-file, +}: +let + inherit (liminix.services) longrun; + inherit (lib.strings) escapeShellArg; +in +longrun { + name = "wpa_supplicant"; + run = + '' + ${wpa_supplicant}/bin/wpa_supplicant -D${driver} -i${interface} -c ${config-file} + ''; +} diff --git a/tests/wlan/wpa_supplicant.nix b/tests/wlan/wpa_supplicant.nix new file mode 100644 index 0000000..98f7f08 --- /dev/null +++ b/tests/wlan/wpa_supplicant.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: +with lib; { + options.system.service.wpa_supplicant = mkOption { type = pkgs.liminix.lib.types.serviceDefn; }; + config.system.service.wpa_supplicant = config.system.callService ./wpa_service.nix { + interface = mkOption { + type = types.str; + }; + driver = mkOption { + type = types.str; + }; + config-file = mkOption { + type = types.package; + }; + }; +}