diff --git a/Makefile b/Makefile index 9c377b2..cae664b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=blue-merle -PKG_VERSION:=1.1.0 +PKG_VERSION:=2.0.0 PKG_RELEASE:=$(AUTORELEASE) PKG_MAINTAINER:=Matthias @@ -12,7 +12,7 @@ include $(INCLUDE_DIR)/package.mk define Package/blue-merle SECTION:=utils CATEGORY:=Utilities - EXTRA_DEPENDS:=gl-ui gl-e750-mcu bash coreutils-shred python3 python3-pyserial patch + EXTRA_DEPENDS:=luci-base, gl-sdk4-mcu, coreutils-shred, python3-pyserial TITLE:=Anonymity Enhancements for GL-E750 Mudi endef @@ -29,8 +29,11 @@ endef define Package/blue-merle/install $(CP) ./files/* $(1)/ $(INSTALL_BIN) ./files/etc/init.d/* $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/etc/gl-switch.d/* $(1)/etc/gl-switch.d/ $(INSTALL_BIN) ./files/lib/blue-merle/mac-wipe.sh $(1)/lib/blue-merle/mac-wipe.sh - $(INSTALL_BIN) ./files/usr/bin/blue-merle $(1)/usr/bin/blue-merle + $(INSTALL_BIN) ./files/usr/bin/* $(1)/usr/bin/ + $(INSTALL_BIN) ./files/usr/libexec/blue-merle $(1)/usr/libexec/blue-merle + $(INSTALL_BIN) ./files/lib/blue-merle/imei_generate.py $(1)/lib/blue-merle/imei_generate.py endef define Package/blue-merle/preinst @@ -42,7 +45,7 @@ define Package/blue-merle/preinst if [ -f "/tmp/sysinfo/model" ] && [ -f "/etc/glversion" ]; then echo "You have a `cat /tmp/sysinfo/model`, running firmware version `cat /etc/glversion`." fi - echo "blue-merle has only been tested with GL-E750 Mudi Version 3.215." + echo "blue-merle has only been tested with GL-E750 Mudi Version 4.3.8." echo "The device or firmware version you are using have not been verified to work with blue-merle." echo -n "Would you like to continue on your own risk? (y/N): " read answer @@ -58,56 +61,14 @@ define Package/blue-merle/preinst fi } - UPDATE_MCU() { - echo "6e6b86e3ad7fec0d5e426eb9a41c51c6f0d6b68a4d341ec553edeeade3e4b470 /tmp/e750-mcu-V1.0.7.bin" > /tmp/e750-mcu.bin.sha256 - wget -O /tmp/e750-mcu-V1.0.7.bin https://github.com/gl-inet/GL-E750-MCU-instruction/blob/master/e750-mcu-V1.0.7-56a1cad7f0eb8318ebe3c3c46a4cf3ff.bin?raw=true - if sha256sum -cs /tmp/e750-mcu.bin.sha256; then - ubus call service delete '{"name":"e750_mcu"}' - mcu_update /tmp/e750-mcu-V1.0.7.bin - else - echo "Failed to update MCU, verification of the binary failed." - echo "Your device needs to be connected to the Internet in order to download the MCU binary." - exit 1 - fi - } - - CHECK_MCUVERSION() { - function version { echo "$$@" | cut -d' ' -f2 | awk -F. '{ printf("%d%03d%03d%03d\n", $$1,$$2,$$3,$$4); }'; } - mcu_version=`echo \{\"version\": \"1\"} > /dev/ttyS0; sleep 0.1; cat /dev/ttyS0|tr -d '\n'` - if [ $$(version "$$mcu_version") -ge $$(version "V 1.0.7") ]; then - return 0 - else - echo - echo "Your MCU version has not been verified to work with blue-merle." - echo "Automatic shutdown may not work." - echo "The install script can initiate an update of the MCU." - echo "The device will reboot and, after reboot, you need to run opkg install blue-merle again." - echo -n "Would you like to update your MCU? (y/N): " - read answer - case $$answer in - Y*) answer=0;; - y*) answer=0;; - *) answer=1;; - esac - if [[ "$$answer" -eq 0 ]]; then - UPDATE_MCU - fi - fi - } - if grep -q "GL.iNet GL-E750" /proc/cpuinfo; then GL_VERSION=$$(cat /etc/glversion) case $$GL_VERSION in - 4.*) - echo Version $$GL_VERSION is not supported - exit 1 - ;; - 3.215) + 4.3.8) echo Version $$GL_VERSION is supported - CHECK_MCUVERSION exit 0 ;; - 3.*) + 4.*) echo Version $$GL_VERSION is *probably* supported ABORT_GLVERSION ;; @@ -120,32 +81,26 @@ define Package/blue-merle/preinst else ABORT_GLVERSION fi + + # Our volatile-mac service gets started during the installation + # but it modifies the client database held by the gl_clients process. + # So we stop that process now, have the database put onto volatile storage + # and start the service after installation + /etc/init.d/gl_clients stop endef define Package/blue-merle/postinst #!/bin/sh + uci set switch-button.@main[0].func='sim' + uci commit switch-button - patch -b /www/src/temple/settings/index.js /lib/blue-merle/patches/index.js.patch - patch -b /www/src/temple/settings/index.html /lib/blue-merle/patches/index.html.patch - patch -b /usr/bin/switchaction /lib/blue-merle/patches/switchaction.patch - patch -b /usr/bin/switch_queue /lib/blue-merle/patches/switch_queue.patch + /etc/init.d/gl_clients start - uci set glconfig.switch_button='service' - uci set glconfig.switch_button.enable='1' - uci set glconfig.switch_button.function='sim' - uci commit glconfig + echo {\"msg\": \"Successfully installed Blue Merle\"} > /dev/ttyS0 endef define Package/blue-merle/postrm #!/bin/sh - - mv /www/src/temple/settings/index.js.orig /www/src/temple/settings/index.js - mv /www/src/temple/settings/index.html.orig /www/src/temple/settings/index.html - mv /usr/bin/switchaction.orig /usr/bin/switchaction - mv /usr/bin/switch_queue.orig /usr/bin/switch_queue - - rm -f /tmp/sim_change_start - rm -f /tmp/sim_change_switch + uci set switch-button.@main[0].func='tor' endef $(eval $(call BuildPackage,$(PKG_NAME))) - diff --git a/files/etc/gl-switch.d/sim.sh b/files/etc/gl-switch.d/sim.sh new file mode 100644 index 0000000..df329b7 --- /dev/null +++ b/files/etc/gl-switch.d/sim.sh @@ -0,0 +1,30 @@ +#!/bin/sh +action=$1 +logger -p notice -t blue-merle-toggle "Called... ${action}" + + +. /lib/functions/gl_util.sh + + + +if [ "$action" = "on" ];then + mcu_send_message "Blue Merle ${action}" + echo "on" > /tmp/sim_change_switch + flock -n /tmp/blue-merle-switch.lock logger -p notice -t blue-merle-toggle "Running Stage 1" || logger -p notice -t blue-merle-toggle "Lockfile busy" + flock -n /tmp/blue-merle-switch.lock timeout 90 /usr/bin/blue-merle-switch-stage1 + +elif [ "$action" = "off" ];then + # We check for any previous run and eventually execute the second stage. We could check for the age of this marker and only activate the second stage is the marker is young enough. + if [ -f /tmp/blue-merle-stage1 ]; then + flock -n /tmp/blue-merle-switch.lock || logger -p notice -t blue-merle-toggle "Lockfile busy" & + flock -n /tmp/blue-merle-switch.lock timeout 90 /usr/bin/blue-merle-switch-stage2 + else + logger -p notice -t blue-merle-toggle "No Stage 1; Toggling Off" + fi + echo "off" > /tmp/sim_change_switch + +else + echo "off" > /tmp/sim_change_switch +fi +logger -p notice -t blue-merle-toggle "Finished Switch $action" +sleep 1 diff --git a/files/etc/init.d/blue-merle b/files/etc/init.d/blue-merle index b3644ca..ecbdc4f 100755 --- a/files/etc/init.d/blue-merle +++ b/files/etc/init.d/blue-merle @@ -2,16 +2,15 @@ . /lib/blue-merle/functions.sh -START=81 +# We intend to be started before the first network-related service is started. +# According to https://openwrt.org/docs/techref/initscripts, /etc/rc.d/ determines +# the order of the services to be started (or stopped). The lower the number, +# the earlier the service is started. +# We observe "repeater" having the value 15. "network" 20. We certainly want to ahead of those. +START=14 STOP=99 - -start() { - /lib/blue-merle/mac-wipe.sh - CHECKMACSYMLINK - RESET_BSSIDS -} - -stop() { - /lib/blue-merle/mac-wipe.sh -} +start() { + RESET_BSSIDS + RANDOMIZE_MACADDR +} diff --git a/files/etc/init.d/volatile-client-macs b/files/etc/init.d/volatile-client-macs new file mode 100644 index 0000000..7b12913 --- /dev/null +++ b/files/etc/init.d/volatile-client-macs @@ -0,0 +1,30 @@ +#!/bin/sh /etc/rc.common + +# MAC addresses of connected clients are stored in a sqlite database. +# Having the database seems to be necessary for the device to be working properly. +# We intent to have the device store the database in RAM rather than on flash. +# We replace the directory with a memory-backed tmpfs which is as volatile as we can make it. + +# We want to run ahead of "gl-tertf" which, currently, has a prioprity of 60. +# We also want to run ahead of "gl_clients" which has 99. +START=59 +STOP=99 + +start() { + tmpdir="$(mktemp -d)" + # We mount a tmpfs so that the client database will be stored in memory only + mount -t tmpfs / "$tmpdir" + cp -a /etc/oui-tertf/client.db "$tmpdir" + shred /etc/oui-tertf/client.db || rm -f /etc/oui-tertf/client.db + # If this script runs multiple times, we accumulate mounts; we try to avoid having mounts over mounts, so we unmount any existing tmpfs + umount -t tmpfs -l /etc/oui-tertf + + mount -t tmpfs / /etc/oui-tertf + cp -a "$tmpdir/client.db" /etc/oui-tertf/client.db + umount -t tmpfs -l "$tmpdir" +} + +stop() { + shred /etc/oui-tertf/client.db || rm -f /etc/oui-tertf/client.db +} + diff --git a/files/lib/blue-merle/functions.sh b/files/lib/blue-merle/functions.sh index 2345439..7c47457 100644 --- a/files/lib/blue-merle/functions.sh +++ b/files/lib/blue-merle/functions.sh @@ -2,27 +2,6 @@ # This script provides helper functions for blue-merle -# check that MAC wiping/linking to dev/null is still in place -CHECKMACSYMLINK () { - local loc_file="/etc/init.d/gl_tertf" - if [ $(readlink -f "$loc_file") == "/dev/null" ] - then - echo "TEST: EXISTS" - else - echo "TEST: DOES NOT EXIST" - cp "$loc_file" "$loc_file.bak" # todo: consider if we need to move this backup elsewhere? - ln -sf /dev/null "$loc_file" - fi -} - -# Restore gl_tertf from back-up -RESTORE_GL_TERTF () { - local loc_file="/etc/init.d/gl_tertf" - local loc_backup="/etc/init.d/gl_tertf.bak" - #local loc_location="/etc/init.d" - rm "$loc_file" - mv "$loc_backup" "$loc_file" -} UNICAST_MAC_GEN () { loc_mac_numgen=`python3 -c "import random; print(f'{random.randint(0,2**48) & 0b111111101111111111111111111111111111111111111111:0x}'.zfill(12))"` @@ -35,7 +14,16 @@ RESET_BSSIDS () { uci set wireless.@wifi-iface[1].macaddr=`UNICAST_MAC_GEN` uci set wireless.@wifi-iface[0].macaddr=`UNICAST_MAC_GEN` uci commit wireless - wifi # need to reset wifi for changes to apply + # you need to reset wifi for changes to apply, i.e. executing "wifi" +} + + +# This chaneges the MAC address clients see when connecting to the WiFi spawned by the device. +# You can check with "arp -a" that your endpoint, e.g. your laptop, sees a different MAC after a reboot of the Mudi. +RANDOMIZE_MACADDR () { + uci set network.@device[1].macaddr=`UNICAST_MAC_GEN` + uci commit network + # You need to restart the network, i.e. /etc/init.d/network restart } READ_IMEI () { @@ -82,11 +70,22 @@ READ_IMSI () { echo $imsi } + +SET_IMEI() { + imei="$1" + + if [[ ${#imei} -eq 14 ]]; then + gl_modem AT AT+EGMR=1,7,${imei} + else + echo "IMEI is ${#imei} not 14 characters long" + fi +} + CHECK_ABORT () { sim_change_switch=`cat /tmp/sim_change_switch` - if [[ "$sim_change_switch" = "off" ]]; then - e750-mcu "SIM change aborted." - sleep 1 - exit 1 + if [[ "$sim_change_switch" = "off" ]]; then + echo '{ "msg": "SIM change aborted." }' > /dev/ttyS0 + sleep 1 + exit 1 fi } diff --git a/files/lib/blue-merle/imei_generate.py b/files/lib/blue-merle/imei_generate.py index 59db66c..39ea788 100644 --- a/files/lib/blue-merle/imei_generate.py +++ b/files/lib/blue-merle/imei_generate.py @@ -17,6 +17,8 @@ class Modes(Enum): ap = argparse.ArgumentParser() ap.add_argument("-v", "--verbose", help="Enables verbose output", action="store_true") +ap.add_argument("-g", "--generate-only", help="Only generates an IMEI rather than setting it", + action="store_true") modes = ap.add_mutually_exclusive_group() modes.add_argument("-d", "--deterministic", help="Switches IMEI generation to deterministic mode", action="store_true") modes.add_argument("-s", "--static", help="Sets user-defined IMEI", @@ -42,7 +44,11 @@ TIMEOUT = 3 def get_imsi(): + if (verbose): + print(f'Obtaining Serial {TTY} with timeout {TIMEOUT}...') with serial.Serial(TTY, BAUDRATE, timeout=TIMEOUT, exclusive=True) as ser: + if (verbose): + print('Getting IMSI') ser.write(b'AT+CIMI\r') # TODO: read loop until we have 'enough' of what to expect output = ser.read(64) @@ -176,10 +182,12 @@ def validate_imei(imei): if __name__ == '__main__': args = ap.parse_args() + imsi_d = None if args.verbose: verbose = args.verbose if args.deterministic: mode = Modes.DETERMINISTIC + imsi_d = get_imsi() if args.random: mode = Modes.RANDOM if args.static is not None: @@ -192,11 +200,11 @@ if __name__ == '__main__': else: exit(-1) else: - imsi_d = get_imsi() imei = generate_imei(imei_prefix, imsi_d) if (verbose): print(f"Generated new IMEI: {imei}") - if not set_imei(imei): - exit(-1) + if not args.generate_only: + if not set_imei(imei): + exit(-1) exit(0) diff --git a/files/lib/blue-merle/luhn.lua b/files/lib/blue-merle/luhn.lua new file mode 100644 index 0000000..6f1b1dc --- /dev/null +++ b/files/lib/blue-merle/luhn.lua @@ -0,0 +1,61 @@ +---- Adapted from https://cybersecurity.att.com/blogs/labs-research/luhn-checksum-algorithm-lua-implementation + +local bit = require("bit") + +local band, bor, bxor = bit.band, bit.bor, bit.bxor + +function luhn_checksum(card) + local num = 0 + local nDigits = card:len() + odd = band(nDigits, 1) + + for count = 0,nDigits-1 do + + digit = tonumber(string.sub(card, count+1,count+1)) + + if (bxor(band(count, 1),odd)) == 0 + then + digit = digit * 2 + end + + if digit > 9 then + digit = digit - 9 + end + + num = num + digit + end + + return num +end + +function luhn_digit (s) + local num = luhn_checksum (s) + return (10 - (num % 10)) +end + +function is_valid_luhn (s) + local num = luhn_checksum (s) + return ((num % 10) == 0) +end + + +function make_imei (premei) + local imei = premei .. tostring(luhn_digit(premei .. "0")) + if is_valid_luhn (imei) then print ("Valid " .. imei) end + + return imei +end + +function make_random_imei () + local nDigits = 13 + local premei = "" + for count = 0, nDigits-1 do + premei = premei .. tostring (math.random (0, 9)) + end + + local imei = make_imei (tostring (premei)) + + return imei +end + +print (make_imei ("354809108035177")) diff --git a/files/lib/blue-merle/mac-wipe.sh b/files/lib/blue-merle/mac-wipe.sh deleted file mode 100644 index 695a5f1..0000000 --- a/files/lib/blue-merle/mac-wipe.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env ash - -# This script wipes all MAC address data from the device and is called upon boot - -tmp_dir="/tmp/tertf" -tmp_file="/tmp/tertf/tertfinfo_bak" - -etc_dir="/etc/tertf" -etc_file="/etc/tertf/tertfinfo_bak" - -# Check for directories -CHECKDIR_TMP () { - if [ -d "$tmp_dir" ]; then - echo "The /tmp/ directory exists." - else - echo "The /tmp/ directory does not exist. This should be fine..." - fi -} - -CHECKDIR_ETC () { - if [ -d "$etc_dir" ]; then - echo "The /etc/ directory exists." - else - echo "The /etc/ directory does not exist. Exiting..." - exit 1 - fi -} - -# trick the gl_tertf file into moving stuff to the void -GASLIGHT () { # good job lil dude you're doing so well - local file="/etc/init.d/gl_tertf" - ln -sf /dev/null "$file" -} - -CHECKDIR_TMP -CHECKDIR_ETC -GASLIGHT - -# Kills process responsible for manipulating (and protecting) the /tmp/ file instance -killall -9 gltertf - -# shredding /tmp/tertf -if [ -f "$tmp_file" ];then - echo "Files found within /tmp/. Let's get to it." - shred -v -u "$tmp_file" -else - echo "No file found within /tmp/tertf. No shredding to be done there." -fi - -# shredding /etc/tertf -if [ -f "$etc_file" ]; then - echo "Files found in /etc/. Let's get to it." - shred -v -u "$etc_file" #-v provides verbose output to ease my anxious mind and -u deletes files after they are overwritten -else - echo "No file found within /etc/tertf. No shredding to be done there." -fi - -# check if the files have been removed -if [ ! -f "$tmp_file" ]; then - echo "Looks like /tmp/ is clean!" - else - echo "Something went wrong in /tmp/." -fi - -if [ ! -f "$etc_file" ]; then - echo "Looks like /etc/ is clean!" - else - echo "Something went wrong in /etc/." -fi - -exit 0 diff --git a/files/lib/blue-merle/patches/index.html.patch b/files/lib/blue-merle/patches/index.html.patch deleted file mode 100644 index f43e9e9..0000000 --- a/files/lib/blue-merle/patches/index.html.patch +++ /dev/null @@ -1,39 +0,0 @@ ---- orig/index.html -+++ patch/index.html -@@ -6,7 +6,7 @@ - - {{t($lang.setting.btnSetting)}} - -- {{t($lang.button.apply)}} -+ {{t($lang.button.apply)}} - -
- -@@ -64,6 +64,20 @@ -
- - -+ -+
-+
-+
SIM Change Start
-+
{{'SIM change will be initiated'}}. -+
-+
-+
-+
SIM Change Stop
-+
-+
-+
-+
-+ - -
- -@@ -84,4 +98,4 @@ - -
- -- -\ No newline at end of file -+ diff --git a/files/lib/blue-merle/patches/index.js.patch b/files/lib/blue-merle/patches/index.js.patch deleted file mode 100644 index 1d2fdd8..0000000 --- a/files/lib/blue-merle/patches/index.js.patch +++ /dev/null @@ -1,41 +0,0 @@ ---- orig/index.js -+++ patch/index.js -@@ -6,7 +6,7 @@ - data: function data() { - return { - isShow: false, -- chooseState: ["No function (default)", "WireGuard® Client Toggle (On/Off)", "OpenVPN Client Toggle (On/Off)"], -+ chooseState: ["No function (default)", "WireGuard® Client Toggle (On/Off)", "OpenVPN Client Toggle (On/Off)", "SIM Change (Start/Stop)"], - // chooseState4G: ["No function (default)", "WireGuard® Client Toggle (On/Off)", "OpenVPN Client Toggle (On/Off)", ], - param: "", - btnStatus: true, -@@ -79,6 +79,9 @@ - case "Tor Toggle (On/Off)": - status = "tor"; - break; -+ case "SIM Change (Start/Stop)": -+ status="sim"; -+ break; - } - return status; - }, -@@ -108,6 +111,9 @@ - case "tor": - _this.param = "Tor Toggle (On/Off)"; - break; -+ case "sim": -+ _this.param = "SIM Change (Start/Stop)"; -+ break; - } - } - }); -@@ -170,6 +176,9 @@ - case 'tor': - _this.openSetting(); - break; -+ case 'sim': -+ _this.openSetting(); -+ break; - } - }, - openSetting: function openSetting() { diff --git a/files/lib/blue-merle/patches/switch_queue.patch b/files/lib/blue-merle/patches/switch_queue.patch deleted file mode 100644 index c216627..0000000 --- a/files/lib/blue-merle/patches/switch_queue.patch +++ /dev/null @@ -1,23 +0,0 @@ ---- orig/switch_queue -+++ patch/switch_queue -@@ -81,6 +81,10 @@ - "tor") - e750-mcu " Turning TOR ON" - ;; -+ "sim") -+# e750-mcu " Starting SIM swap" -+ blue-merle-switch& -+ ;; - "*") - ;; - esac -@@ -98,6 +102,9 @@ - check_other_vpn tor - e750-mcu " Turning TOR OFF" - ;; -+ "sim") -+# e750-mcu " Stopping SIM swap" -+ ;; - "*") - ;; - esac diff --git a/files/lib/blue-merle/patches/switchaction.patch b/files/lib/blue-merle/patches/switchaction.patch deleted file mode 100644 index a57e8de..0000000 --- a/files/lib/blue-merle/patches/switchaction.patch +++ /dev/null @@ -1,38 +0,0 @@ ---- orig/switchaction -+++ patch/switchaction -@@ -10,6 +10,15 @@ - fi - } - -+toggle_sim(){ -+ local action=$1 -+ if [ "$action" = "OFF" ];then -+ sim_switch off -+ else -+ sim_switch on -+ fi -+} -+ - check_other_vpn(){ - wg_server=$(uci get wireguard_server.@servers[0].enable) - ov_server=$(uci get vpn_service.global.enable) -@@ -136,6 +145,9 @@ - check_other_vpn tor - toggle_tor ON - ;; -+ "sim") -+ toggle_sim ON -+ ;; - "*") - ;; - esac -@@ -157,6 +169,9 @@ - "tor") - toggle_tor OFF - ;; -+ "sim") -+ toggle_sim OFF -+ ;; - "*") - ;; - esac diff --git a/files/usr/bin/blue-merle b/files/usr/bin/blue-merle index 09e1317..2563180 100644 --- a/files/usr/bin/blue-merle +++ b/files/usr/bin/blue-merle @@ -44,6 +44,14 @@ while [[ "$answer" -eq 1 ]]; do fi done +## We have just disabled the modem so it should not log on to any network. +## We set a random IMEI now only to have it overwritten very soon after +## the SIM card has been replaced. We intend to prevent an accidential +## leak of the new SIM's IMSI with the old IMEI just in case the modem +## accidentally tried to log in to the network. +python3 /lib/blue-merle/imei_generate.py -r + + echo -n "Please now replace the SIM card and press any key to continue. " read answer @@ -112,7 +120,8 @@ case $answer in *) answer=1;; esac if [[ "$answer" -eq 1 ]]; then - echo {\"poweroff\": \"1\"} >/tmp/mcu_message && sleep 0.5 && killall -17 e750-mcu + echo '{ "msg": "Shutting down..." }' > /dev/ttyS0 + halt -d 5 else echo "Resetting modem..." until gl_modem AT AT+QPOWD | grep -q OK diff --git a/files/usr/bin/blue-merle-switch b/files/usr/bin/blue-merle-switch deleted file mode 100755 index 4758181..0000000 --- a/files/usr/bin/blue-merle-switch +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/sh - -. /lib/blue-merle/functions.sh - -if [ ! -c "/dev/ttyUSB3" ]; then - e750-mcu "Error: /dev/ttyUSB3 does not exist." - sleep 3 - e750-mcu "Please reboot & contact maintainer if problem persists." - exit 1; -fi - -if [ ! -f "/tmp/sim_change_start" ]; then - echo 0 > /tmp/sim_change_start -fi - -if [ ! -f "/tmp/sim_change_switch" ]; then - sim_switch off -fi - -now=$(date +%s) -sim_change_last=`cat /tmp/sim_change_start` -sim_change_diff=$((now-sim_change_last)) - -if [[ "$sim_change_diff" -lt 60 ]]; then - e750-mcu "Please wait >1min between two SIM swaps. ($sim_change_diff s)" - exit 1 -fi - -echo "$now" > /tmp/sim_change_start - -e750-mcu "Starting SIM swap." -sleep 3 - -i=5 -until [[ $i -lt 0 ]] -do - e750-mcu "Pull switch to abort ($i). " - i=$((i-1)) - sleep 1 - - CHECK_ABORT -done - -e750-mcu "Continuing ..." -sleep 1 - -e750-mcu "Disabling the MEfrom transmit- ting and recei- ving RF signals." -sleep 3 - -old_imei=$(READ_IMEI) -old_imsi=$(READ_IMSI) - -CHECK_ABORT - -answer=1 -while [[ "$answer" -eq 1 ]]; do - gl_modem AT AT+CFUN=4 | grep -q OK - if [[ $? -eq 1 ]]; then - e750-mcu "Disabling failed. Trying again." - CHECK_ABORT - else - answer=0 - e750-mcu "Disabled." - sleep 2 - fi -done - -e750-mcu "Replace the SIM card. Then pull the switch." - -while [[ `cat /tmp/sim_change_switch` = "on" ]]; do - e750-mcu "Replace the SIM card. Then pull the switch." - sleep 3 -done - -e750-mcu "Switch pulled. Continuing..." -sleep 1 -sim_switch on - -leak=0 - -until gl_modem AT AT+CFUN=0 | grep -q OK -do - e750-mcu "CFUN=0 failed. Trying again." - sleep 1 -done - -until gl_modem AT AT+CFUN=4 | grep -q OK -do - leak=1 - e750-mcu "CFUN=4 failed. Trying again." - sleep 1 -done - -if [[ $leak -eq 1 ]]; then - e750-mcu "WARNING: Reset took longer than expected." - sleep 3 -fi - -sleep 1 - -new_imsi=$(READ_IMSI) - -if [[ "$old_imsi" == "$new_imsi" ]]; then - e750-mcu "WARNING: Old IMSI equals new IMSI." - sleep 3 -fi - -e750-mcu "Setting random IMEI" -python3 /lib/blue-merle/imei_generate.py -r - -new_imei=$(READ_IMEI) - -if [[ "$old_imei" == "$new_imei" ]]; then - e750-mcu "WARNING: Old IMEI equals new IMEI." - sleep 3 -else - mkdir -p /tmp/modem.1-1.2 - echo "$new_imei" > /tmp/modem.1-1.2/modem-imei -fi - - -e750-mcu "The device will shutdown now." -sleep 3 -e750-mcu "You should change your location before booting again." -sleep 5 - - -i=5 -until [[ $i -eq 0 ]] -do - i=$((i-1)) - e750-mcu "Shutting down... ($i)" - sleep 1 -done - -echo {\"poweroff\": \"1\"} >/tmp/mcu_message && sleep 0.5 && killall -17 e750-mcu - -exit 0 diff --git a/files/usr/bin/blue-merle-switch-stage1 b/files/usr/bin/blue-merle-switch-stage1 new file mode 100644 index 0000000..901fca1 --- /dev/null +++ b/files/usr/bin/blue-merle-switch-stage1 @@ -0,0 +1,73 @@ +#!/bin/sh + +. /lib/blue-merle/functions.sh +. /lib/functions/gl_util.sh + +if [ ! -f "/tmp/sim_change_start" ]; then + echo 0 > /tmp/sim_change_start +fi + +if [ ! -f "/tmp/sim_change_switch" ]; then + sim_switch off +fi + +now=$(date +%s) +sim_change_last=`cat /tmp/sim_change_start` +sim_change_diff=$((now-sim_change_last)) + +if [[ "$sim_change_diff" -lt 60 ]]; then + mcu_send_message "Please wait >1min between two SIM swaps. ($sim_change_diff s)" + exit 1 +fi + +echo "$now" > /tmp/sim_change_start + +mcu_send_message "Starting SIM swap." +sleep 3 + + +## We're disabling this abort functionality for the moment because the switch keeps being blocked and we cannot notice the pulled switch. We could abort by default and require another toggle to continue. +#i=5 +#until [[ $i -lt 0 ]] +#do +# mcu_send_message "Pull switch to abort ($i). " +# i=$((i-1)) +# sleep 1 +# +# CHECK_ABORT +#done +# +#mcu_send_message "Continuing ..." +#sleep 1 + +mcu_send_message "Disabling the ME from transmitting and receiving RF signals." +sleep 3 + +old_imei=$(READ_IMEI) +old_imsi=$(READ_IMSI) + +#CHECK_ABORT + +answer=1 +while [[ "$answer" -eq 1 ]]; do + gl_modem AT AT+CFUN=4 | grep -q OK + if [[ $? -eq 1 ]]; then + mcu_send_message "Disabling failed. Trying again." + CHECK_ABORT + else + answer=0 + mcu_send_message "Disabled." + sleep 2 + fi +done + +## We generate a random IMEI to prevent a leak of the +## new SIM's IMSI under the old IMEI in case the modem +## still talks to the network +python3 /lib/blue-merle/imei_generate.py -r + + +mcu_send_message "Replace the SIM card. Then pull the switch." + +echo done > /tmp/blue-merle-stage1 +logger -p notice -t blue-merle-toggle "Finished with Stage 1" diff --git a/files/usr/bin/blue-merle-switch-stage2 b/files/usr/bin/blue-merle-switch-stage2 new file mode 100644 index 0000000..85335e5 --- /dev/null +++ b/files/usr/bin/blue-merle-switch-stage2 @@ -0,0 +1,71 @@ +#!/bin/sh + +. /lib/blue-merle/functions.sh +. /lib/functions/gl_util.sh + +rm -f /tmp/blue-merle-stage1 + +mcu_send_message "Switch pulled. Continuing..." +sleep 1 +sim_switch on + +leak=0 + +until gl_modem AT AT+CFUN=0 | grep -q OK +do + mcu_send_message "CFUN=0 failed. Trying again." + sleep 1 +done + +until gl_modem AT AT+CFUN=4 | grep -q OK +do + leak=1 + mcu_send_message "CFUN=4 failed. Trying again." + sleep 1 +done + +if [[ $leak -eq 1 ]]; then + mcu_send_message "WARNING: Reset took longer than expected." + sleep 3 +fi + +sleep 1 + +new_imsi=$(READ_IMSI) + +if [[ "$old_imsi" == "$new_imsi" ]]; then + mcu_send_message "WARNING: Old IMSI equals new IMSI." + sleep 3 +fi + +mcu_send_message "Setting random IMEI" +python3 /lib/blue-merle/imei_generate.py -r + +new_imei=$(READ_IMEI) + +if [[ "$old_imei" == "$new_imei" ]]; then + mcu_send_message "WARNING: Old IMEI equals new IMEI." + sleep 3 +else + mkdir -p /tmp/modem.1-1.2 + echo "$new_imei" > /tmp/modem.1-1.2/modem-imei +fi + +logger -p notice -t blue-merle-toggle "Changed IMEI from ${old_imei} to ${new_imei}" + + +mcu_send_message "The device will shutdown now." +sleep 3 +mcu_send_message "You should change your location before booting again." +sleep 5 + + +i=5 +/sbin/poweroff -d $i +until [[ $i -eq 0 ]] +do + i=$((i-1)) + mcu_send_message "Shutting down... ($i)" + sleep 1 +done +logger -p notice -t blue-merle-toggle "Finished with Stage 2" diff --git a/files/usr/libexec/blue-merle b/files/usr/libexec/blue-merle new file mode 100755 index 0000000..6d9ace4 --- /dev/null +++ b/files/usr/libexec/blue-merle @@ -0,0 +1,50 @@ +#!/bin/sh + +. /lib/blue-merle/functions.sh + + +show_message() { + # There is mcu_send_message() in /lib/functions/gl_util.sh but we don't want to load the file, thinking that it will take too long + echo {\"msg\": \"$1\"} > /dev/ttyS0 +} + + +logger -p notice -t blue-merle-libexec "Libexec $1" + +if [ "$1" == "read-imei" ]; then + imei="$(READ_IMEI)" + echo -n $imei + show_message "My IMEI: $imei" + +elif [ "$1" == "read-imsi" ]; then + imsi="$(READ_IMSI)" + if [ "x$imsi" == "x" ]; then + echo "No IMSI found $imsi" >&2 + exit 1 + else + echo -n $imsi + show_message "My IMSI: $imsi" + fi + +elif [ "$1" == "random-imei" ]; then + flock -n /tmp/blue-merle-imei-generate.lock timeout 15 /lib/blue-merle/imei_generate.py --random + READ_IMEI + +elif [ "$1" == "shutdown-modem" ]; then + exec gl_modem AT AT+CFUN=4 + +elif [ "$1" == "shutdown" ]; then + echo '{ "msg": "Shutdowing down..." }' > /dev/ttyS0 + halt -d 5 + echo -n "Shutting down" + poweroff -d 5 + logger -p notice -t blue-merle-libexec "Shutting down" + +elif [ "$1" == "write-imei" ]; then + new_imei=$2 + echo -n { "action": "write" } +else + echo -n '{"msg":"Hello, World!"}' + #echo 'foo'>&2 + echo 0 +fi diff --git a/files/usr/share/luci/menu.d/luci-app-blue-merle.json b/files/usr/share/luci/menu.d/luci-app-blue-merle.json new file mode 100644 index 0000000..9b16180 --- /dev/null +++ b/files/usr/share/luci/menu.d/luci-app-blue-merle.json @@ -0,0 +1,13 @@ +{ + "admin/network/blue-merle": { + "title": "Blue Merle", + "order": 30, + "action": { + "type": "view", + "path": "blue-merle" + }, + "depends": { + "acl": [ "luci-app-blue-merle" ] + } + } +} diff --git a/files/usr/share/rpcd/acl.d/luci-app-blue-merle.json b/files/usr/share/rpcd/acl.d/luci-app-blue-merle.json new file mode 100644 index 0000000..8e9ff2c --- /dev/null +++ b/files/usr/share/rpcd/acl.d/luci-app-blue-merle.json @@ -0,0 +1,26 @@ +{ + "luci-app-blue-merle": { + "description": "Grant access to opkg management", + "read": { + "cgi-io": [ "exec" ], + "file": { + "/usr/libexec/blue-merle": [ "exec" ], + "/usr/libexec/blue-merle shred": [ "exec" ], + "/usr/libexec/blue-merle *": [ "exec" ], + "/etc/opkg.conf": [ "read" ], + "/etc/opkg/*.conf": [ "read" ] + }, + "ubus": { + "luci": [ "getMountPoints" ] + } + }, + "write": { + "file": { + "/usr/libexec/blue-merle": [ "exec" ], + "/usr/libexec/blue-merle shred": [ "exec" ], + "/usr/libexec/blue-merle *": [ "exec" ], + "/tmp/upload.ipk": [ "write" ] + } + } + } +} diff --git a/files/www/luci-static/resources/view/blue-merle.js b/files/www/luci-static/resources/view/blue-merle.js new file mode 100644 index 0000000..eaa7598 --- /dev/null +++ b/files/www/luci-static/resources/view/blue-merle.js @@ -0,0 +1,421 @@ +'use strict'; +'require view'; +'require fs'; +'require ui'; +'require rpc'; + +var css = ' \ + .controls { \ + display: flex; \ + margin: .5em 0 1em 0; \ + flex-wrap: wrap; \ + justify-content: space-around; \ + } \ + \ + .controls > * { \ + padding: .25em; \ + white-space: nowrap; \ + flex: 1 1 33%; \ + box-sizing: border-box; \ + display: flex; \ + flex-wrap: wrap; \ + } \ + \ + .controls > *:first-child, \ + .controls > * > label { \ + flex-basis: 100%; \ + min-width: 250px; \ + } \ + \ + .controls > *:nth-child(2), \ + .controls > *:nth-child(3) { \ + flex-basis: 20%; \ + } \ + \ + .controls > * > .btn { \ + flex-basis: 20px; \ + text-align: center; \ + } \ + \ + .controls > * > * { \ + flex-grow: 1; \ + align-self: center; \ + } \ + \ + .controls > div > input { \ + width: auto; \ + } \ + \ + .td.version, \ + .td.size { \ + white-space: nowrap; \ + } \ + \ + ul.deps, ul.deps ul, ul.errors { \ + margin-left: 1em; \ + } \ + \ + ul.deps li, ul.errors li { \ + list-style: none; \ + } \ + \ + ul.deps li:before { \ + content: "↳"; \ + display: inline-block; \ + width: 1em; \ + margin-left: -1em; \ + } \ + \ + ul.deps li > span { \ + white-space: nowrap; \ + } \ + \ + ul.errors li { \ + color: #c44; \ + font-size: 90%; \ + font-weight: bold; \ + padding-left: 1.5em; \ + } \ + \ + ul.errors li:before { \ + content: "⚠"; \ + display: inline-block; \ + width: 1.5em; \ + margin-left: -1.5em; \ + } \ +'; + +var isReadonlyView = !L.hasViewPermission() || null; + +var callMountPoints = rpc.declare({ + object: 'luci', + method: 'getMountPoints', + expect: { result: [] } +}); + +var packages = { + available: { providers: {}, pkgs: {} }, + installed: { providers: {}, pkgs: {} } +}; + +var languages = ['en']; + +var currentDisplayMode = 'available', currentDisplayRows = []; + + + +function handleReset(ev) +{ +} + + +function callBlueMerle(arg) { + const cmd = "/usr/libexec/blue-merle"; + var prom = fs.exec(cmd, [arg]); + return prom.then( + function(res) { + console.log("Blue Merle arg", arg, "res", res); + if (res.code != 0) { + throw new Error("Return code " + res.code); + } else { + return res.stdout; + } + } + ).catch( + function(err) { + console.log("Error calling Blue Merle", arg, err); + throw err; + } + ); +} + +function readIMEI() { + return callBlueMerle("read-imei"); +} + +function randomIMEI() { + callBlueMerle("random-imei").then( + function(res){ + readIMEI().then( + console.log("new IMEI", imei) + ); + } + ).catch( + function(err){ + console.log("Error", err); + } + ); +} + +function readIMSI() { + return callBlueMerle("read-imsi"); +} + +function handleConfig(ev) +{ + var conf = {}; + + const cmd = "/usr/libexec/blue-merle"; + var dlg = ui.showModal(_('Executing blue merle'), [ + E('p', { 'class': 'spinning' }, + _('Waiting for the %h command to complete…').format(cmd)) + ]); + + var argv = ["random-imei"]; + console.log("Calling ", cmd, argv); + // FIXME: Investigate whether we should be using fs.exec() + fs.exec_direct(cmd, argv, 'text').then(function(res) { + console.log("Res:", res, "stdout", res.stdout, "stderr", res.stderr, "code", res.code); + + if (res.stdout) + dlg.appendChild(E('pre', [ res.stdout ])); + + if (res.stderr) { + dlg.appendChild(E('h5', _('Errors'))); + dlg.appendChild(E('pre', { 'class': 'errors' }, [ res.stderr ])); + } + + console.log("Res.code: ", res.code); + if (res.code !== 0) + dlg.appendChild(E('p', _('The %h %h command failed with code %d.').format(cmd, argv, (res.code & 0xff) || -1))); + + dlg.appendChild(E('div', { 'class': 'right' }, + E('div', { + 'class': 'btn', + 'click': L.bind(function(res) { + if (ui.menu && ui.menu.flushCache) + ui.menu.flushCache(); + + ui.hideModal(); + + if (res.code !== 0) + rejectFn(new Error(res.stderr || 'opkg error %d'.format(res.code))); + else + resolveFn(res); + }, this, res) + }, _('Dismiss')))); + }).catch(function(err) { + ui.addNotification(null, E('p', _('Unable to execute opkg %s command: %s').format(cmd, err))); + ui.hideModal(); + }); + + + + fs.list('/etc/opkg').then(function(partials) { + var files = [ '/etc/opkg.conf' ]; + + for (var i = 0; i < partials.length; i++) + if (partials[i].type == 'file' && partials[i].name.match(/\.conf$/)) + files.push('/etc/opkg/' + partials[i].name); + + return Promise.all(files.map(function(file) { + return fs.read(file) + .then(L.bind(function(conf, file, res) { conf[file] = res }, this, conf, file)) + .catch(function(err) { + }); + })); + }).then(function() { + var body = [ + E('p', {}, _('Below is a listing of the various configuration files used by opkg. Use opkg.conf for global settings and customfeeds.conf for custom repository entries. The configuration in the other files may be changed but is usually not preserved by sysupgrade.')) + ]; + + Object.keys(conf).sort().forEach(function(file) { + body.push(E('h5', {}, '%h'.format(file))); + body.push(E('textarea', { + 'name': file, + 'rows': Math.max(Math.min(L.toArray(conf[file].match(/\n/g)).length, 10), 3) + }, '%h'.format(conf[file]))); + }); + + body.push(E('div', { 'class': 'right' }, [ + E('div', { + 'class': 'btn cbi-button-neutral', + 'click': ui.hideModal + }, _('Cancel')), + ' ', + E('div', { + 'class': 'btn cbi-button-positive', + 'click': function(ev) { + var data = {}; + findParent(ev.target, '.modal').querySelectorAll('textarea[name]') + .forEach(function(textarea) { + data[textarea.getAttribute('name')] = textarea.value + }); + + ui.showModal(_('OPKG Configuration'), [ + E('p', { 'class': 'spinning' }, _('Saving configuration data…')) + ]); + + Promise.all(Object.keys(data).map(function(file) { + return fs.write(file, data[file]).catch(function(err) { + ui.addNotification(null, E('p', {}, [ _('Unable to save %s: %s').format(file, err) ])); + }); + })).then(ui.hideModal); + }, + 'disabled': isReadonlyView + }, _('Save')), + ])); + + //ui.showModal(_('OPKG Configuration'), body); + }); +} + +function handleShutdown(ev) +{ + return callBlueMerle("shutdown") +} + +function handleRemove(ev) +{ +} + +function handleSimSwap(ev) { + const spinnerID = 'swap-spinner-id'; + var dlg = ui.showModal(_('Starting SIM swap...'), + [ + E('p', { 'class': 'spinning', 'id': spinnerID }, + _('Shutting down modem…') + ) + ] + ); + callBlueMerle("shutdown-modem").then( + function(res) { + dlg.appendChild( + E('pre', { 'class': 'result'}, + res + ) + ); + dlg.appendChild( + E('p', { 'class': 'text'}, + _("Generating Random IMEI") + ) + ); + callBlueMerle("random-imei").then( + function(res) { + document.getElementById(spinnerID).style = "render: none"; + dlg.appendChild( + E('div', { 'class': 'text'}, + [ + E('p', { 'class': 'text'}, + _("IMEI set:") + " " + res + ), + E('p', { 'class': 'text'}, + _("Please shutdown the device and go to another place before booting") + ), + E('button', { 'class': 'btn cbi-button-positive', 'click': handleShutdown, 'disabled': isReadonlyView }, + [ _('Shutdown…') ] + ) + ] + ) + ) + } + ).catch( + function(err) { + dlg.appendChild( + E('p',{'class': 'error'}, + _('Error setting IMEI! ') + err + ) + ) + } + ); + } + ).catch( + function(err) { + dlg.appendChild( + E('p',{'class': 'error'}, + _('Error! ') + err + ) + ) + } + ); +} + +function handleOpkg(ev) +{ +} + +function handleUpload(ev) +{ +} + + +function handleInput(ev) { +} + +return view.extend({ + load: function() { + }, + + render: function(listData) { + var query = decodeURIComponent(L.toArray(location.search.match(/\bquery=([^=]+)\b/))[1] || ''); + + const imeiInputID = 'imei-input'; + const imsiInputID = 'imsi-input'; + + var view = E([], [ + E('style', { 'type': 'text/css' }, [ css ]), + + E('h2', {}, _('Blue Merle')), + + E('div', { 'class': 'controls' }, [ + E('div', {}, [ + E('label', {}, _('IMEI') + ':'), + E('span', { 'class': 'control-group' }, [ + E('input', { 'id':imeiInputID, 'type': 'text', 'name': 'filter', 'placeholder': _('e.g. 31428392718429'), 'minlength':14, 'maxlenght':14, 'required':true, 'value': query, 'input': handleInput, 'disabled': true }) + //, E('button', { 'class': 'btn cbi-button', 'click': handleReset }, [ _('Clear') ]) + //, E('button', { 'class': 'btn cbi-button', 'click': randomIMEI }, [ _('Set Random') ]) + ]) + ]), + + E('div', {}, [ + E('label', {}, _('IMSI') + ':'), + E('span', { 'class': 'control-group' }, [ + E('input', { 'id':imsiInputID, 'type': 'text', 'name': 'filter', 'placeholder': _('e.g. 31428392718429'), 'minlength':14, 'maxlenght':14, 'required':true, 'value': query, 'input': handleInput, 'disabled': true }) + //, E('button', { 'class': 'btn cbi-button', 'click': handleReset }, [ _('Clear') ]) + ]) + ]), + ]), + + E('div', {}, [ + E('label', {}, _('Actions') + ':'), ' ', + E('span', { 'class': 'control-group' }, [ + E('button', { 'class': 'btn cbi-button-positive', 'data-command': 'update', 'click': handleSimSwap, 'disabled': isReadonlyView }, [ _('SIM swap…') ]), ' ' + //, E('button', { 'class': 'btn cbi-button-action', 'click': handleUpload, 'disabled': isReadonlyView }, [ _('IMEI change…') ]), ' ' + //, E('button', { 'class': 'btn cbi-button-neutral', 'click': handleConfig }, [ _('Shred config…') ]) + ]) + ]) + + ]); + + readIMEI().then( + function(imei) { + console.log("My controllolol", imei); + const e = document.getElementById(imeiInputID); + console.log("Input: ", e, e.placeholder, e.value); + e.value = imei; + } + ).catch( + function(err){ + console.log("Errrrrr", err) + } + ) + + readIMSI().then( + function(imsi) { + const e = document.getElementById(imsiInputID); + e.value = imsi; + } + ).catch( + function(err){ + const e = document.getElementById(imsiInputID); + e.value = "No IMSI found"; + } + ) + + return view; + }, + + handleSave: null, + handleSaveApply: null, + handleReset: null +});